Skip to content

17. Βιβλιοθήκες

Σύνοψη Η πρότυπη βιβλιοθήκη της C, το αρχείο επικεφαλίδας stdio.h, οι συναρτήσεις snprintf(), scanf(), perror(), το αρχείο επικεφαλίδας stdlib.h, οι συναρτήσεις calloc(), realloc(), abort(), atexit(), srand(), rand(), system(), bsearch(), qsort(), το αρχείο επικεφαλίδας string.h, οι συναρτήσεις strncat(), strncmp(), strncpy(), strdup(), strndup(), memset(), memcmp(), memcpy(), memmove(), το αρχείο επικεφαλίδας math.h, οι συναρτήσεις ceil(), floor(), trunc(), round(), modf(), fmod(), sin(), cos(), tan(), asin(), acos(), atan(), το αρχείο επικεφαλίδας tgmath.h, το αρχείο επικεφαλίδας time.h, οι τύποι clock_t, struct tm, οι συναρτήσεις ctime(), difftimef(), gmtime(), localtime(), asctime(), strftime(), η σταθερά CLOCKS_PER_SEC, εξωτερικές βιβλιοθήκες, το λογισμικό vcpkg, παράδειγμα κλήσης συναρτήσεων της βιβλιοθήκης GSL.

Προαπαιτούμενη γνώση Τύποι δεδομένων, είσοδος/έξοδος, δομές επιλογής και επανάληψης, συναρτήσεις, πίνακες, δείκτες, αλφαριθμητικά, διαμερισμός κώδικα.

17.1 Η πρότυπη βιβλιοθήλκη της C

Η πρότυπη βιβλιοθήκη της C, η libc, είναι μια συλλογή από συναρτήσεις, μακροεντολές και τύπους που αποτελεί τμήμα των προδιαγραφών της γλώσσας. Οι συναρτήσεις της πρότυπης βιβλιοθήκης υλοποιούν βασικές λειτουργίες που χρησιμοποιούνται συχνά και καθώς συμπεριλαμβάνονται στη γλώσσα δεν χρειάζεται να υλοποιηθούν εκ νέου από τον προγραμματιστή ούτε να αναζητηθούν εξωτερικές βιβλιοθήκες για αυτές. Η πρότυπη βιβλιοθήκη περιλαμβάνει διάφορα αρχεία επικεφαλίδων όπως τα stdio.h, stdlib.h, string.h, math.h, time.h και άλλα, με το καθένα να παρέχει διαφορετικά σύνολα συναρτήσεων, τύπων και μακροεντολών.

17.1.1 stdio.h

Το αρχείο επικεφαλίδας stdio.h περιέχει συναρτήσεις όπως οι printf(), scanf(), fopen(), fclose(), fprintf() και fscanf() που είδαμε σε προηγούμενα κεφάλαια. Το stdio.h περιέχει πολλές άλλες χρήσιμες συναρτήσεις. Στη συνέχεια θα παρουσιαστούν 3 από αυτές, η snprintf(), η sscanf() και η perror().

snprintf() Η συνάρτηση snprintf() λειτουργεί παρόμοια με την printf(), αλλά αποθηκεύει το μορφοποιημένο αλφαριθμητικό που παράγει σε ένα αλφαριθμητικό αντί να το εμφανίσει στην οθόνη. Τα τρία πρώτα υποχρεωτικά ορίσματα που δέχεται είναι: α) ένα αλφαριθμητικό εξόδου, β) το μέγιστο πλήθος χαρακτήρων που θα γραφούν στο αλφαριθμητικό εξόδου, συμπεριλαμβανομένου του συμβόλου '\0', και γ) ένα αλφαριθμητικό μορφοποίησης, όπως αυτά που χρησιμοποιεί η printf(). Επιστρέφει το πλήθος των χαρακτήρων που γράφονται στο αλφαριθμητικό εξόδου. Στον κώδικα 17.1 παρουσιάζεται ένα απλό παράδειγμα που εγγράφει σε ένα αλφαριθμητικό έναν ακέραιο και δύο πραγματικούς αριθμούς. Στη συνέχεια, το αλφαριθμητικό εμφανίζεται στην οθόνη.

Κώδικας 17.1: ch17_p1.c - χρήση αλφαριθμητικού για την έξοδο τιμών.
#include <stdio.h>

int main(void) {
  char output_string[100];
  int a = 10;
  float b = 2.73f;
  double c = 3.14;
  int d = snprintf(output_string, sizeof(output_string),
                   "a=%d, b=%.2f, c=%.2lf", a, b, c);
  printf("Characters written=%d\n", d);
  printf("%s\n", output_string);
  return 0;
}

Ακολουθούν τα αποτελέσματα εκτέλεσης του κώδικα:

Characters written=20
a=10, b=2.73, c=3.14

Αξίζει να σημειωθεί ότι ισοδύναμη λειτουργικότητα παρέχει και η συνάρτηση sprintf(), αλλά πλέον είναι deprecated (δηλαδή η χρήση της δεν συνίσταται και μπορεί να αποσυρθεί σε μελλοντικές εκδόσεις). Η προειδοποίηση που επιστρέφει ο μεταγλωττιστής gcc, αν χρησιμοποιηθεί ο διακόπτης ‐fsanitize=address, είναι να μη χρησιμοποιείται η sprintf(), λόγω του ότι δεν παρέχει έλεγχο για πιθανή υπερχείλιση του αλφαριθμητικού στο οποίο κατευθύνεται η έξοδος.

sscanf() Η συνάρτηση sscanf() λειτουργεί παρόμοια με την scanf(), αλλά αντί να διαβάζει την είσοδο από το πληκτρολόγιο, διαβάζει από ένα αλφαριθμητικό. Δέχεται δύο κύρια ορίσματα, ένα αλφαριθμητικό εισόδου και ένα αλφαριθμητικό μορφοποίησης που ορίζει την αναμενόμενη μορφή των δεδομένων εισόδου, όπως στην scanf(). Η sscanf() διαβάζει δεδομένα από το αλφαριθμητικό εισόδου και αναθέτει τιμές με βάση το αλφαριθμητικό μορφοποίησης σε αντίστοιχες μεταβλητές που περνούν ως επιπλέον ορίσματα μετά το αλφαριθμητικό μορφοποίησης. Η συνάρτηση επιστρέφει το πλήθος των εισόδων που αντιστοιχίστηκαν επιτυχώς και ανατέθηκαν ή EOF αν συμβεί κάποιο σφάλμα (π.χ. κενό αλφαριθμητικό εισόδου). Ο κώδικας 17.2 είναι ένα παράδειγμα χρήσης της sscanf(), όπου η είσοδος τιμών για 3 μεταβλητές δίνεται από ένα αλφαριθμητικό.

Κώδικας 17.2: ch17_p2.c - ανάγνωση εισόδου από ένα αλφαριθμητικό.
#include <stdio.h>

int main(void) {
  char input_string[] = "Jane Doe 25";
  char first_name[20];
  char last_name[20];
  int age;

  int r = sscanf(input_string, "%s %s %d", first_name, last_name, &age);
  if (r == EOF) {
    printf("Error in parsing input data\n");
  } else {
    printf("%d values read.\n", r);
    printf("First Name: %s, Last Name: %s, Age: %d\n", first_name, last_name,
           age);
  }
  return 0;
}

Η εκτέλεση του προγράμματος θα δώσει την ακόλουθη έξοδο:

3 values read.
First Name: Jane, Last Name: Doe, Age: 25

perror() Ορισμένες συναρτήσεις της πρότυπης βιβλιοθήκης, αλλά και συναρτήσεις άλλων βιβλιοθηκών, έχουν γραφεί έτσι ώστε όταν προκαλείται ένα σφάλμα η καθολική μεταβλητή errno που ορίζεται στο errno.h να λαμβάνει μια αναγνωριστική τιμή σφάλματος. Η διαδικασία που ακολουθείται για τον χειρισμό λαθών που θέτουν τιμή στην errno είναι η ακόλουθη:

  1. Κλήση συνάρτησης που μπορεί να προκαλέσει κάποια κατάσταση σφάλματος.
  2. Αν το σφάλμα προκληθεί, τότε η συνάρτηση επιστρέφει μια τιμή που υποδηλώνει ότι συνέβη πρόβλημα (π.χ. NULL ή -1 ή κάποια άλλη τιμή).
  3. Ο κώδικας που κάλεσε τη συνάρτηση ελέγχει την τιμή του errno για να καθορίσει τον τύπο του σφάλματος.
  4. Χειρισμός λάθους (π.χ. εμφάνιση μηνύματος λάθους, καταγραφή λάθους, τερματισμός προγράμματος).

Η συνάρτηση perror() χρησιμοποιείται κυρίως για να εμφανίσει μηνύματα αποσφαλμάτωσης. Η κλήση της εμφανίζει ένα μήνυμα που ορίζει ο προγραμματιστής που ακολουθείται από τον χαρακτήρα : και την περιγραφή του πλέον πρόσφατου σφάλματος που συνέβη και έθεσε την τιμή του errno.
Παραδείγματα συναρτήσεων που μπορεί να προκαλέσουν σφάλματα κατά την κλήση τους είναι η fopen() της stdio.h, η malloc() της stdlib.h, η read() της unistd.h και άλλες. Στον κώδικα 17.3 παρουσιάζεται ένα παράδειγμα που η fopen() θέτει την τιμή του errno επιχειρώντας να ανοίξει ένα αρχείο που δεν εντοπίζεται.

Κώδικας 17.3: ch17_p3.c - αποτυχημένο άνοιγμα αρχείου και χειρισμός του λάθους που προκύπτει.
#include <errno.h>
#include <stdio.h>

int main(void) {
  FILE *fp = fopen("non_existent_file.txt", "r");
  if (fp == NULL) {
    printf("errno=%d\n", errno);
    perror("Error opening file");
  } else {
    fclose(fp);
  }
  return 0;
}

Η έξοδος του προγράμματος, δεδομένου ότι το αρχείο non_existent_file.txt δεν υπάρχει στο σύστημα είναι:

errno=2
Error opening file: No such file or directory

17.1.2 stdlib.h

Το stdlib.h περιέχει συναρτήσεις για δέσμευση και αποδέσμευση μνήμης όπως η malloc() και η free(), συναρτήσεις για έλεγχο διεργασιών όπως η exit(), συναρτήσεις μετατροπής αλφαριθμητικού σε ακέραιο όπως η atoi(), συναρτήσεις παραγωγής τυχαίων τιμών όπως η rand() και η srand(), καθώς και άλλες συναρτήσεις. Στη συνέχεια θα εξεταστούν μερικές βασικές δυνατότητες που προσφέρει η stdlib.h.

Δυναμική δέσμευση μνήμης Εκτός από τη malloc() υπάρχουν και άλλες συναρτήσεις δυναμικής δέσμευσης μνήμης όπως η calloc() και η realloc(), όπως είδαμε στην παράγραφο 9.1.5. H calloc() δέχεται δύο ορίσματα, το πλήθος των στοιχείων που θα δεσμεύσει σε συνεχόμενες θέσεις μνήμης και το μέγεθος σε bytes κάθε στοιχείου. Επιστρέφει έναν δείκτη προς το πρώτο byte του μπλοκ μνήμης που δεσμεύει, αρχικοποιώντας όλα τα bytes σε μηδέν. Αν αποτύχει η δέσμευση μνήμης, η calloc() επιστρέφει την τιμή NULL. Η realloc() χρησιμοποιείται για να αλλάξει μέγεθος σε μνήμη που έχει δεσμευθεί σε προηγούμενη δέσμευση μνήμης. Δέχεται ως ορίσματα έναν δείκτη προς δεσμευμένη μνήμη και το μέγεθος της μνήμης που θα δεσμευθεί. Η realloc(), στην περίπτωση που αυξάνει το μέγεθος της μνήμης που δεσμεύει σε σχέση με πριν, διατηρεί τα δεδομένα της προηγούμενης δεσμευμένης μνήμης. Ακολουθεί, στον κώδικα 17.4, ένα παράδειγμα που αρχικά δεσμεύεται δυναμικά χώρος μνήμης για έναν πίνακα 5 ακεραίων, με την calloc() αρχικοποιώντας όλες τις τιμές του πίνακα με μηδέν. Στη συνέχεια, με τη realloc() ο δεσμευμένος χώρος επεκτείνεται έτσι ώστε να διατηρεί πλέον 10 θέσεις ακεραίων

Κώδικας 17.4: ch17_p4.c - αρχική δυναμική δέσμευση πίνακα και μετέπειτα επέκτασή του με τη realloc().
#include <stdio.h>
#include <stdlib.h>

void print_array(int *a, int N) {
  for (int i = 0; i < N; i++) {
    printf("%d ", a[i]);
  }
  printf("\n");
}

int main(void) {
  int num_elements = 5;
  int *arr = (int *)calloc(num_elements, sizeof(int));
  if (arr == NULL) {
    printf("Initial memory allocation failed!\n");
    exit(1);
  }
  printf("Initial memory allocation successful!\n");
  print_array(arr, num_elements);
  num_elements = 10;
  // Αλλαγή μεγέθους πίνακα για να χωρά 10 τιμές
  int *resized_arr = (int *)realloc(arr, num_elements * sizeof(int));
  if (resized_arr == NULL) {
    printf("Memory reallocation failed!\n");
    exit(1);
  }
  printf("Memory reallocation successful!\n");
  arr = resized_arr; // Ενημέρωση του δείκτη προς το νέο πίνακα
  print_array(arr, num_elements);
  free(arr); // απελευθέρωση μνήμης, είτε πέτυχε η αλλαγή μεγέθους είτε όχι
  return 0;
}

Η εκτέλεση του προγράμματος δίνει τα ακόλουθα αποτελέσματα:

Initial memory allocation successful!
0 0 0 0 0
Memory reallocation successful!
0 0 0 0 0 0 0 0 0 0

Αξίζει να σημειωθεί ότι οι 5 πρώτες τιμές του πίνακα αναμένεται σε κάθε εκτέλεση να έχουν μηδενική τιμή (πριν και μετά την αλλαγή μεγέθους). Οι υπόλοιπες θέσεις του πίνακα, αν και συμβαίνει στο συγκεκριμένο παράδειγμα εκτέλεσης, δεν υπάρχει εγγύηση ότι θα είναι μηδενικές.

Χειρισμός τερματισμού προγραμμάτων Μια άλλη ομάδα συναρτήσεων είναι οι exit(), abort() και atexit() που πραγματοποιούν τερματισμό του προγράμματος και την εκτέλεση ενεργειών πριν τον τερματισμό του. Η συνάρτηση exit() προκαλεί κανονικό τερματισμό του προγράμματος και επιστροφή του ελέγχου στο λειτουργικό σύστημα. Δέχεται ως όρισμα μια ακέραια τιμή, την κατάσταση εξόδου (exit status), που υποδηλώνει την κατάσταση με την οποία τερμάτισε την εκτέλεσή του το πρόγραμμα. Η τιμή αυτή μπορεί να ληφθεί με εντολές που θα αναφερθούν στη συνέχεια σε συστήματα Linux, MacOS ή Windows και μπορούν να χρησιμοποιούνται, για παράδειγμα, σε shell scripts για να καθοδηγήσουν την εκτέλεση μιας ομάδας ενεργειών. Στο ακόλουθο παράδειγμα (κώδικας 17.5) καλείται είτε η exit() είτε η abort().

Κώδικας 17.5: ch17_p5.c - κλήση των συναρτήσεων exit() και abort(), ανάλογα με την είσοδο του χρήστη.
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int choice;
  printf("How do you want to stop the program (1=exit, 2=abort, 3=return)? ");
  scanf("%d", &choice);
  if (choice == 1) {
    printf("Let's set the exit status to 99.\n");
    exit(99);
  } else if (choice == 2) {
    printf("Abort.\n");
    abort();
  }
  return 0;
}

Ακολουθούν αποτελέσματα 3 εκτελέσεων του κώδικα. Σε κάθε περίπτωση, η κατάσταση εξόδου του προγράμματος (αν έχει τεθεί) λαμβάνεται με την εντολή echo $? σε Linux ή σε MacOS. Αν η έξοδος έχει γίνει με την exit() ή την return() τότε η κατάσταση εξόδου είναι η τιμή του ορίσματος που έχει περάσει στη συνάρτηση. Αν η έξοδος γίνεται με το abort(), τότε δεν λαμβάνεται πίσω κάποια τιμή με νόημα ως κατάσταση εξόδου του προγράμματος (για παράδειγμα, η συγκεκριμένη εκτέλεση επιστρέφει την τιμή 134).

$ ./ch17_p5
How do you want to stop the program (1=exit, 2=abort, 3=return)? 1
Let's set the exit status to 99.
$ echo $?
99
$ ./ch17_p5
How do you want to stop the program (1=exit, 2=abort, 3=return)? 2
Abort.
zsh: abort  ch17_p5
$ echo $?
134
$ ./ch17_p5
How do you want to stop the program (1=exit, 2=abort, 3=return)? 3
$ echo $?
0

Αν το πρόγραμμα μεταγλωττιστεί και εκτελεστεί σε Windows, τότε η κατάσταση εξόδου λαμβάνεται με την εντολή:

> echo %ERRORLEVEL%

Επιπλέον, η συνάρτηση atexit() μπορεί να χρησιμοποιηθεί έτσι ώστε να εκτελεστεί κάποιος κώδικας όταν το πρόγραμμα τερματίσει κανονικά την εκτέλεσή του. Η συνάρτηση atexit() δέχεται ως όρισμα μια συνάρτηση που θα εκτελεστεί όταν το πρόγραμμα τερματιστεί επιτυχώς και συνήθως χρησιμοποιείται για αποδέσμευση πόρων και εμφάνιση μηνυμάτων. Στον κώδικα 17.6 παρουσιάζεται ένα παράδειγμα χρήσης της atexit().

Κώδικας 17.6: ch17_p6.c - κλήση συνάρτησης ακριβώς πριν τον τερματισμό εκτέλεσης του προγράμματος.
#include <stdio.h>
#include <stdlib.h>

void cleanup(void) { printf("Program terminated blissfully!\n"); }

int main(void) {
  printf("Program Started\n");
  atexit(cleanup);
  printf("Ready to exit (y/n)? ");
  char choice;
  scanf("%c", &choice);
  if (choice == 'y' || choice == 'Y') {
    exit(1);
  } else if (choice == 'n' || choice == 'N') {
    abort();
  }
  return 0;
}

Ακολουθεί ένα παράδειγμα εκτέλεσης.

Program Started
Ready to exit (y/n)? y
Program terminated blissfully!

Δημιουργία τυχαίων αριθμών Οι δύο συναρτήσεις του stdlib.h που επιτρέπουν τη δημιουργία ψευδοτυχαίων τιμών είναι η srand() και η rand(). H srand(), δέχεται ως όρισμα το λεγόμενο seed (σπόρος). Αν παραλειφθεί, χρησιμοποιείται ως seed η τιμή 1. Αν εκτελεστεί μια διαδικασία παραγωγής τυχαίων αριθμών και χρησιμοποιηθεί το ίδιο seed, τότε οι ψευδοτυχαίες τιμές που θα εμφανιστούν θα είναι οι ίδιες με την προηγούμενη εκτέλεση. Για να λαμβάνονται διαφορετικές τιμές σε κάθε εκτέλεση, συνήθως χρησιμοποιείται η τιμή που επιστρέφει η κλήση time(NULL) που όπως θα δούμε και στη συνέχεια του ίδιου κεφαλαίου, επιστρέφει το πλήθος δευτερολέπτων της χρονικής στιγμής που πραγματοποιείται η εκτέλεση από την ημερομηνία και ώρα 1/1/1970 00:00:00. Η rand() επιστρέφει μια τυχαία ακέραια τιμή από 0 μέχρι RAND_MAX. H RAND_MAX ορίζεται στη stdlib.h και το πρότυπο της γλώσσας αναφέρει ότι θα πρέπει να έχει ελάχιστη τιμή 215 − 1 = 32767. Ωστόσο, σε πολλές σύγχρονες υλοποιήσεις η τιμή του RAND_MAX είναι η μέγιστη τιμή που μπορεί να λάβει ένας προσημασμένος ακέραιος 32-bit, που είναι 231 − 1 = 2147483647. Στον κώδικα 17.7 παρουσιάζεται ένα παράδειγμα δημιουργίας 10 ακεραίων τιμών, επί δύο φορές, με κάθε ακέραια τιμή να βρίσκεται στο διάστημα από 1 έως και 10.

Κώδικας 17.7: ch17_p7.c - δημιουργία 10 τυχαίων τιμών με seed την τιμή 42 και δημιουργία άλλων 10 τυχαίων τιμών με seed μια τιμή που αλλάζει σε κάθε εκτέλεση.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  srand(42); // για επαναληψιμότητα αποτελεσμάτων χρησιμοποιείται ένα seed,
             // εδώ η τιμή 42
  for (int i = 0; i < 10; i++) {
    int r = rand() % 10 + 1;
    printf("%2d ", r);
  }
  printf("\n");
  srand(time(NULL)); // το seed αλλάζει σε κάθε εκτέλεση του προγράμματος
  for (int i = 0; i < 10; i++) {
    int r = rand() % 10 + 1;
    printf("%2d ", r);
  }
  printf("\n");
  return 0;
}

Ακολουθούν τα αποτελέσματα που παράγονται από δύο εκτελέσεις του κώδικα. Η πρώτη εκτέλεση εμφανίζει:

  5  4  10  4  7   2  2  1  1  6
 10  5   6  6  2  10  1  9  9  8

Η δεύτερη εκτέλεση εμφανίζει:

 5  4  10  4  7   2  2  1  1  6
 7  8   9  5  8  10  2  6  1  8

Η πρώτη γραμμή της εξόδου είναι ίδια και στις δύο περιπτώσεις (διότι έχει χρησιμοποιηθεί το ίδιο seed), ενώ η δεύτερη διαφέρει.
Ένα μειονέκτημα της ανάθεσης τιμής στο seed με το time(NULL) είναι ότι η τιμή που επιστρέφει το time(NULL) αλλάζει σχετικά αργά (κάθε δευτερόλεπτο), οπότε αν το πρόγραμμα εκτελείται ξανά σε διάστημα μικρότερο του ενός δευτερολέπτου, οι τυχαίες τιμές θα είναι οι ίδιες. Για να αποφευχθεί αυτή η συμπεριφορά μπορεί να χρησιμοποιηθεί η συνάρτηση gettimeofday() από το sys/time.h που επιστρέφει το χρονικό διάστημα από 1/1/1970 00:00:00 μέχρι την τρέχουσα χρονική στιγμή με ακρίβεια μικροδευτερολέπτων. Ο κώδικας 17.8 παρουσιάζει την παραγωγή 5 ομάδων πραγματικών τυχαίων αριθμών στο διάστημα από 0 μέχρι 1, χρησιμοποιώντας τη συνάρτηση gettimeofday().

Κώδικας 17.8: ch17_p8.c - δημιουργία τυχαίων τιμών με seed τιμή που επιστρέφει η συνάρτηση gettimeofday().
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

int main(void) {
  struct timeval current_time;
  gettimeofday(&current_time, NULL);
  long seed = current_time.tv_usec;
  srand(seed);
  printf("SEED = %ld\n", seed);
  for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 10; j++) {
      double r = (double)rand() / (double)RAND_MAX;
      printf("%.4lf ", r);
    }
    printf("\n");
  }
  return 0;
}

Ακολουθεί ένα παράδειγμα εκτέλεσης:

SEED = 774255
0.0596 0.7902 0.1987 0.1579 0.6974 0.2448 0.5293 0.4014 0.6387 0.6481
0.4010 0.7232 0.0741 0.6294 0.9382 0.0232 0.7664 0.3592 0.4847 0.9366
0.6032 0.2064 0.1106 0.1082 0.9458 0.2407 0.2765 0.0262 0.5820 0.3237
0.4546 0.6432 0.5426 0.6371 0.0795 0.9908 0.5744 0.6008 0.6509 0.2925
0.0924 0.4719 0.4286 0.5220 0.5141 0.4083 0.3387 0.9178 0.0553 0.7129

Αλληλεπίδραση με το λειτουργικό σύστημα Η συνάρτηση system() επιτρέπει την εκτέλεση προγραμμάτων που θα μπορούσαν να εκτελεστούν από τη γραμμή εντολών. Ο κώδικας 17.9 είναι ένα παράδειγμα που ανάλογα με το είδος του λειτουργικού συστήματος (Linux, Windows, MacOS), εκτελείται μια εντολή που εμφανίζει πληροφορίες για το υλικό του συστήματος.

Κώδικας 17.9: ch17_p9.c - επιστροφή πληροφοριών συστήματος.
#include <stdio.h>
#include <stdlib.h>

int main(void) {
#ifdef __linux__
  printf("Hardware Information:\n");
  system("lscpu");
#elif __APPLE__
  printf("System Information:\n");
  system("system_profiler SPHardwareDataType");
#elif _WIN32
  printf("System Information:\n");
  system("systeminfo");
#else
  printf("Unsupported platform.\n");
#endif
  return 0;
}

Η ακόλουθη έξοδος είναι τα αποτέλεσμα της εκτέλεσης σε ένα σύστημα MacOS:

System Information:
Hardware:
    Hardware Overview:
        Model Name: Mac mini
        Model Identifier: Mac14 ,12
        Model Number: MNH73LL/A
        Chip: Apple M2 Pro
        Total Number of Cores: 10 (6 performance and 4 efficiency)
        Memory: 16 GB
        System Firmware Version: 10151.1.1
        OS Loader Version: 10151.1.1
        Serial Number (system): XXXXXXXXX
        Hardware UUID: XXXX‐XXXX‐XXXX‐XXXX‐XXXXXXXXXXX
        Provisioning UDID: XXXXXXXX ‐XXXXXXXXXXXX
        Activation Lock Status: Enabled

Συνάρτηση γρήγορης ταξινόμησης και συνάρτηση δυαδικής αναζήτησης Οι συναρτήσεις qsort() και bsearch() πραγματοποιούν ταξινόμηση και αναζήτηση αντίστοιχα. Η qsort() υλοποιεί τον αλγόριθμο γρήγορης ταξινόμησης και μπορεί να χρησιμοποιηθεί για να ταξινομήσει αριθμούς, αλφαριθμητικά, και σύνθετες δομές. Έχει τέσσερις παραμέτρους: έναν δείκτη προς τον πίνακα που θα ταξινομηθεί, το πλήθος των στοιχείων του πίνακα, το μέγεθος κάθε στοιχείου σε bytes και μια συνάρτηση σύγκρισης. Η συνάρτηση σύγκρισης καλείται από την qsort() κάθε φορά που πρέπει να συγκριθούν δύο στοιχεία. Αν επιστρέψει αρνητική τιμή σημαίνει ότι το πρώτο στοιχείο είναι μικρότερο του δεύτερου, αν επιστρέψει μηδέν σημαίνει ότι είναι ίσα και αν επιστρέψει θετική τιμή σημαίνει ότι το πρώτο στοιχείο είναι μεγαλύτερο του δεύτερου. Συνεπώς, ο προγραμματιστής πρέπει να γράψει την κατάλληλη συνάρτηση σύγκρισης και να την περάσει ως τέταρτο όρισμα στην qsort() κατά την κλήση της. Η συνάρτηση bsearch() υλοποιεί τον αλγόριθμο δυαδικής αναζήτησης που εντοπίζει τη θέση μιας τιμής σε έναν πίνακα. Πρόκειται για έναν ταχύτατο αλγόριθμο, αλλά απαιτεί ο πίνακας στον οποίο γίνεται η αναζήτηση να είναι διατεταγμένος σε αύξουσα σειρά. Η bsearch() έχει πέντε παραμέτρους: την τιμή αναζήτησης, έναν δείκτη προς τον πίνακα στον οποίο θα γίνει αναζήτηση, το πλήθος των στοιχείων του πίνακα, το μέγεθος κάθε στοιχείου σε bytes και μια συνάρτηση σύγκρισης. Στο παράδειγμα του κώδικα 17.10, χρησιμοποιούνται και οι δύο συναρτήσεις. Αρχικά, ένας πίνακας ταξινομείται σε αύξουσα σειρά και στη συνέχεια ζητείται για μια συγκεκριμένη τιμή εάν υπάρχει στον πίνακα. Η συνάρτηση σύγκρισης cmp() που περνά ως τελευταίο όρισμα και στις δύο συναρτήσεις, δέχεται με τη σειρά της ως ορίσματα δείκτες προς ακεραίους, οπότε στο σώμα της πραγματοποιείται αποαναφορά και στους δύο δείκτες για να επιστραφεί το σωστό αποτέλεσμα.

Κώδικας 17.10: ch17_p10.c - ταξινόμηση πίνακα και μετά δυαδική αναζήτηση για εντοπισμό της θέσης μιας τιμής στον πίνακα.
#include <stdio.h>
#include <stdlib.h>

// Συνάρτηση σύγκρισης για την qsort και την bsearch
int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); }

int main(void) {
  int array[10] = {20, 5, 10, 3, 15, 7, 30, 25, 1, 40};
  int key = 25; // Το στοιχείο που αναζητούμε
  int *found;

  // Ταξινόμηση του πίνακα με την qsort
  qsort(array, 10, sizeof(int), cmp);

  printf("Ταξινομημένος πίνακας: ");
  for (int i = 0; i < 10; i++) {
    printf("%d ", array[i]);
  }
  printf("\n");

  // Δυαδική αναζήτηση με την bsearch
  found = (int *)bsearch(&key, array, 10, sizeof(int), cmp);

  if (found != NULL) {
    printf("Το στοιχείο %d βρέθηκε στη θέση %ld.\n", key,
           (found - array)); // ή found - &array[0]
  } else {
    printf("Το στοιχείο %d δεν βρέθηκε.\n", key);
  }

  return 0;
}

Το αποτέλεσμα της εκτέλεσης του προγράμματος θα είναι:

Ταξινομημένος πίνακας: 1 3 5 7 10 15 20 25 30 40
Το στοιχείο 25 βρέθηκε στη θέση 7.

17.1.3 string.h

Στο κεφάλαιο 8, στην παράγραφο 8.6, παρουσιάστηκαν 4 βασικές συναρτήσεις που ορίζονται στο string.h, η strlen() για υπολογισμό του μήκους ενός αλφαριθμητικού, η strcat() για συνένωση αλφαριθμητικών, η strcmp() για σύγκριση αλφαριθμητικών και η strtok() για διαχωρισμό ενός αλφαριθμητικού σε τμήματα. Για τις strcat(), strcmp() καθώς και για τη strcpy() που πραγματοποιεί αντιγραφή ενός αλφαριθμητικού σε ένα άλλο, υπάρχουν άλλες εκδόσεις τους που το όνομά τους σχηματίζεται παρεμβάλλοντας το γράμμα n μετά τα τρία πρώτα γράμματα (str) των συναρτήσεων. Έτσι, προκύπτουν οι συναρτήσεις strncat(), strncmp(), strncpy() που δέχονται ένα επιπλέον όρισμα n και εφαρμόζουν την αντίστοιχη ενέργεια στους n πρώτους χαρακτήρες μόνο. Οι συναρτήσεις αυτές θεωρούνται ασφαλείς, καθώς δεν επιτρέπουν την υπερχείλιση των αλφαριθμητικών που δέχονται ως όρισμα εξόδου. Στον κώδικα 17.11, παρουσιάζεται ένα παράδειγμα χρήσης των συναρτήσεων που αναφέρθηκαν.

Κώδικας 17.11: ch17_p11.c - χρήση ασφαλών συναρτήσεων για χειρισμό αλφαριθμητικών.
#include <stdio.h>
#include <string.h>

int main(void) {
  const char *source =
      "Controlling complexity is the essence of computer programming.";
  char result[80] = "";
  // αντιγραφή των 22 πρώτων χαρακτήρων από το source στο result
  printf("Original=%s\n", source);
  strncpy(result, source, 22);
  printf("Partially copied=%s\n", result);
  // συνένωση των πρώτων 5 χαρακτήρων του source στο result
  strncat(result, source + 22,
          4); // συνένωση του " is " στο τέλος του result
  strncat(result, source + 50,
          12); // συνένωση του " programming." στο τέλος του result
  printf("Concatenated=%s\n", result);
  // σύγκριση τμημάτων λεκτικών
  printf("Comparisons using the first 2 letters of each string:\n");
  char *buffer[] = {"ABCD", "ABEF", "BCDE"};
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      int cmp_result = strncmp(buffer[i], buffer[j], 2);
      if (cmp_result == 0) {
        printf("%s and %s are equal.\n", buffer[i], buffer[j]);
      } else if (cmp_result < 0) {
        printf("%s is less than %s.\n", buffer[i], buffer[j]);
      } else {
        printf("%s is greater than %s.\n", buffer[i], buffer[j]);
      }
    }
  }
  return 0;
}

Η κλήση της συνάρτησης strncpy() στη γραμμή 10 αντιγράφει τους 22 πρώτους χαρακτήρες από το αλφαριθμητικό source στο αλφαριθμητικό result, προσθέτοντας και τον χαρακτήρα '\0' στη θέση 23. Στη συνέχεια, δύο διαδοχικές κλήσεις της strncat() προσθέτουν στο τέλος του αλφαριθμητικού result τμήματα του αλφαριθμητικού source. Τέλος, για κάθε συνδυασμό ανά δύο των 3 αλφαριθμητικών της γραμμής 20, πραγματοποιείται σύγκριση εξετάζοντας μόνο τους 2 πρώτους χαρακτήρες κάθε αλφαριθμητικού και εμφανίζεται η λεξικογραφική σειρά μεταξύ τους. Η έξοδος του προγράμματος είναι:

Original=Controlling complexity is the essence of computer programming.
Partially copied=Controlling complexity
Concatenated=Controlling complexity is programming.
Comparisons using the first 2 letters of each string:
ABCD and ABCD are equal.
ABCD and ABEF are equal.
ABCD is less than BCDE.
ABEF and ABCD are equal.
ABEF and ABEF are equal.
ABEF is less than BCDE.
BCDE is greater than ABCD.
BCDE is greater than ABEF.
BCDE and BCDE are equal.

Η συνάρτηση strdup() Οι strdup() και strndup() είναι δύο συναρτήσεις που πρόσφατα προστέθηκαν στο string.h (με το πρότυπο C23), αν και υποστηρίζονται από τους διαδεδομένους μεταγλωττιστές της C εδώ και πολλά χρόνια. Η strdup() επιστρέφει έναν δείκτη σε ένα αλφαριθμητικό που τερματίζεται με '\0', που είναι αντίγραφο του αλφαριθμητικού που δέχεται ως όρισμα. Καθώς ο δείκτης που επιστρέφεται δείχνει προς θέσεις μνήμης που έχουν δεσμευθεί στον σωρό, θα πρέπει να ακολουθεί εντολή απελευθέρωσης της μνήμης, όταν πλέον δεν χρησιμοποιείται. Στον κώδικα 17.12 παρουσιάζεται ένα παράδειγμα χρήσης των συναρτήσεων strdup() και strndup(). Στη γραμμή 8 ορίζεται μια τοπική μεταβλητή αλφαριθμητικού που παύει να υπάρχει στη γραμμή 12. H strdup() δημιουργεί ένα αντίγραφο των περιεχομένων της t στον σωρό και επιστρέφει έναν δείκτη που αποθηκεύεται στο s1. Παρόμοια, η stdndup() δημιουργεί αντίγραφο μόνο των 25 πρώτων χαρακτήρων του t στον σωρό. Η συνάρτηση strcpy() αντιγράφει το t στον πίνακα s3 που διατηρείται στη στοίβα. Οι γραμμές 16, 17, με δύο κλήσεις της free(), απελευθερώνουν τη μνήμη που έχει δεσμευθεί έμμεσα από τις strdup() και strndup().

Κώδικας 17.12: ch17_p12.c - κλήση της συνάρτησης strdup() και της strndup().
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
  char *s1, *s2, s3[80];
  {
    const char *t = "First, solve the problem. Then, write the code.";
    s1 = strdup(t);
    s2 = strndup(t, 25); // διπλότυπο των 25 πρώτων χαρακτήρων του t
    strcpy(s3, t);
  }
  printf("string at %p: %s\n", (void *)s1, s1);
  printf("string at %p: %s\n", (void *)s2, s2);
  printf("string at %p: %s\n", (void *)s3, s3);
  free(s1);
  free(s2);
  return 0;
}

Ακολουθεί η έξοδος της εκτέλεσης του κώδικα. Οι διευθύνσεις που εμφανίζονται, και αφορούν τα αλφαριθμητικά s1, s2 και s3, υποδηλώνουν ότι τα s1 και s2 βρίσκονται σε διαφορετική περιοχή μνήμης από το s3 (σωρό και στοίβα αντίστοιχα).

string at 0x130606b10: First, solve the problem. Then, write the code.
string at 0x130606a70: First, solve the problem.
string at 0x16ce9ae18: First, solve the problem. Then, write the code.

Συναρτήσεις χειρισμού μπλοκ μνήμης Μια χρήσιμη ομάδα συναρτήσεων του string.h, που χειρίζονται μπλοκ μνήμης αντί για αλφαριθμητικά, αποτελείται από συναρτήσεις όπως οι memset(), memcmp() και memcpy(). Στον κώδικα 17.13 δίνεται ένα παράδειγμα χρήσης αυτών των συναρτήσεων.

Κώδικας 17.13: ch17_p13.c - εργασία με αλφαριθμητικά με τις συναρτήσεις memset(), memcpy() και memcmp().
#include <stdio.h>
#include <string.h>

int main(void) {
  char source[] = "Never trust a computer you can't throw out a window";
  char destination[80];
  // αρχικοποίηση του destination με '*'
  memset(destination, '*', 79);
  destination[79] = '\0';
  printf("After memset, the destination is:\n%s\n", destination);
  // αντιγραφή δεδομένων από το source στο destination έτσι ώστε το source να
  // τοποθετηθεί στη μέση του destination
  memcpy(destination + (80 - strlen(source)) / 2, source, strlen(source));
  printf("After memcpy, the destination is:\n%s\n", destination);
  // σύγκριση περιεχομένων θέσεων μνήμης
  char *buffer = "BAD";
  for (size_t i = 0; i < strlen(buffer); i++) {
    int cmp_result = memcmp(buffer, buffer + i, strlen(buffer + i));
    if (cmp_result == 0) {
      printf("%s and %s are equal.\n", buffer, buffer + i);
    } else if (cmp_result < 0) {
      printf("%s is less than %s.\n", buffer, buffer + i);
    } else {
      printf("%s is greater than %s.\n", buffer, buffer + i);
    }
  }
  return 0;
}

Ακολουθεί το αποτέλεσμα της εκτέλεσης του κώδικα:

After memset, the destination is:
*******************************************************************************
After memcpy, the destination is:
**************Never trust a computer you can't throw out a window**************
BAD and BAD are equal.
BAD is greater than AD.
BAD is less than D.

Η διαφορά των συναρτήσεων memcpy() και strcpy() είναι ότι ενώ η memcpy() αντιγράφει ένα πλήθος bytes από ένα μπλοκ μνήμης σε ένα άλλο, η strcpy() αντιγράφει τα περιεχόμενα ενός αλφαριθμητικού σε ένα άλλο αλφαριθμητικό. Έτσι, ενώ η memcpy() εφαρμόζεται σε μπλοκ μνήμης, η strcpy() εφαρμόζεται σε αλφαριθμητικά.
Το string.h ορίζει και τη συνάρτηση memmove( που έχει παρόμοια λειτουργικότητα με τη memcpy() καθώς και οι δύο αντιγράφουν ένα μπλοκ μνήμης σε ένα άλλο. Ωστόσο, αν τα δύο αυτά μπλοκ μνήμης επικαλύπτονται τότε μόνο η memmove( δίνει εγγυημένα σωστά αποτελέσματα, ενώ η memcpy() παρουσιάζει ακαθόριστη συμπεριφορά (undefined behavior).

17.1.4 math.h

Το math.h περιέχει τριγωνομετρικές συναρτήσεις όπως οι sin(), cos(), tan(), λογαριθμικές συναρτήσεις όπως οι log(), log10(), και άλλες μαθηματικές συναρτήσεις όπως η sqrt(), η cbrt() και η pow() για υπολογισμό τετραγωνικής ρίζας, κυβικής ρίζας και ύψωσης σε δύναμη αντίστοιχα. Στη συνέχεια θα παρουσιαστούν ορισμένες συναρτήσεις του math.h ξεκινώντας με τις συναρτήσεις ceil(), floor(), round() και trunc(). Η συνάρτηση ceil() δέχεται ως παράμετρο μια πραγματική τιμή x και επιστρέφει τον μικρότερο ακέραιο αριθμό που είναι μεγαλύτερος ή ίσος του x. Αντίστοιχα, η floor() επιστρέφει τον μεγαλύτερο ακέραιο που είναι μικρότερος ή ίσος του x. Η round() κάνει στρογγυλοποίηση, ενώ η trunc() αφαιρεί (truncate) το κλασματικό μέρος. Ο πίνακας 17.1 συνοψίζει τη συμπεριφορά των συναρτήσεων.

Πίνακας 17.1: Διαφορές μεταξύ των συναρτήσεων ceil(), floor(), trunc() και round().
Συνάρτηση Λειτουργία Αποτέλεσμα για θετικό x Αποτέλεσμα για αρνητικό x Παραδείγματα
ceil(x) Στρογγυλοποίηση προς τα πάνω, στον πλησιέστερο ακέραιο Μικρότερος ακέραιος που είναι μεγαλύτερος ή ίσος του x Διατήρηση του ακέραιου μέρους ceil(2.3)=3.0, ceil(-2.3)=-2.0
floor(x) Στρογγυλοποίηση προς τα κάτω, στον πλησιέστερο ακέραιο Μεγαλύτερος ακέραιος που είναι μικρότερος ή ίσος του x Μεγαλύτερος ακέραιος που είναι μικρότερος ή ίσος του x floor(2.3)=2.0, floor(-2.3)=-3.0
trunc(x) Αφαίρεση του δεκαδικού μέρους Αφαίρεση του δεκαδικού μέρους του x Αφαίρεση του δεκαδικού μέρους του x trunc(2.3)=2.0, trunc(-2.3)=-2.0
round(x) Στρογγυλοποίηση προς τον πλησιέστερο ακέραιο Αν το κλασματικό μέρος < 0.5 τότε προς τα κάτω, αλλιώς προς τα πάνω Αν το κλασματικό μέρος < 0.5, στρογγυλοποίηση προς τα πάνω, αλλιώς προς τα κάτω round(2.3)=2.0, round(2.7)=3.0, round(-2.3)=-2.0, round(-2.7)=-3.0

Σχετική είναι και η συνάρτηση modf() που χωρίζει μια πραγματική τιμή σε ακέραιο και δεκαδικό μέρος. Το ακόλουθο πρόγραμμα (κώδικας 17.14) δείχνει τη χρήση αυτών των συναρτήσεων.

Κώδικας 17.14: ch17_p14.c - παραδείγματα με συναρτήσεις στρογγυλοποίησης, αποκοπής δεκαδικών κ.λπ.
#include <math.h>
#include <stdio.h>

void fun(double x) {
  double c = ceil(x);
  double f = floor(x);
  double r = round(x);
  double t = trunc(x);
  double integer_part, fractional_part;
  fractional_part = modf(x, &integer_part);
  printf("%5.1f\t%6.1lf\t%7.1lf\t%5.1f\t%5.1f\t%12.1f\t%15.1f\n", x, f, c, r, t,
         integer_part, fractional_part);
}

int main(void) {
  printf(
      "    x\t floor\tceiling\tround\ttrunc\tinteger_part\tfractional_part\n");
  fun(2.3);
  fun(-2.3);
  fun(2.7);
  fun(-2.7);
  return 0;
}

Ακολουθεί η έξοδος του προγράμματος.

    x   floor   ceiling round   trunc   integer_part    fractional_part
  2.3     2.0       3.0   2.0     2.0            2.0                0.3
 ‐2.3    ‐3.0      ‐2.0  ‐2.0    ‐2.0           ‐2.0               ‐0.3
  2.7     2.0       3.0   3.0     2.0            2.0                0.7
 ‐2.7    ‐3.0      ‐2.0  ‐3.0    ‐2.0           ‐2.0               ‐0.7

Μια άλλη συνάρτηση του math.h είναι η fmod(), που υπολογίζει το υπόλοιπο της διαίρεσης δύο αριθμών κινητής υποδιαστολής. Ειδικότερα, όπως αναφέρεται στην τεκμηρίωση της συνάρτησης(1), η fmod(x, y) επιστρέφει την τιμή 𝑥 − 𝑖 ∗ 𝑦 για κάποιον ακέραιο 𝑖τέτοιο ώστε, αν το 𝑦 δεν είναι μηδενικό, το αποτέλεσμα να έχει το ίδιο πρόσημο με το 𝑥 και τιμή μικρότερη ή ίση της τιμής του 𝑦. Υπενθυμίζεται ότι η ακέραια διαίρεση και το ακέραιο υπόλοιπο της διαίρεσης, υπολογίζονται μέσω των τελεστών / και %, όταν οι τελεστέοι είναι ακέραιοι. Στο ακόλουθο παράδειγμα (κώδικας 17.15) φαίνεται η χρήση της fmod().

  1. Η εμφάνιση της περιγραφής της λειτουργίας της συνάρτησης fmod() γίνεται με την εντολή “man fmod” στη γραμμή εντολών σε συστήματα Linux ή MacOS.
Κώδικας 17.15: ch17_p15.c - υπόλοιπο διαίρεσης δύο πραγματικών αριθμών για διαίρεση με ακέραιο πηλίκο.
#include <math.h>
#include <stdio.h>

int main(void) {
  double x = 10.0, y = 4.4;
  int d = floor(x / y);
  double r = fmod(x, y);
  printf("The value %.1f fits %d times in %.1f and the remainder is %.1f\n", y,
         d, x, r);
  return 0;
}

Το πρόγραμμα κατά την εκτέλεσή του θα εμφανίσει:

The value 4.4 fits 2 times in 10.0 and the remainder is 1.2

Οι τριγωνομετρικές συναρτήσεις του math.h αποτελούν μια ακόμη κατηγορία συναρτήσεών του. Πέρα από τις συναρτήσεις sin(), cos(), tan() για ημίτονο, συνημίτονο και εφαπτομένη, που αναφέρθηκαν ήδη, υπάρχουν οι συναρτήσεις asin(), acos(), atan() και atan2(), που υπολογίζουν τόξα τριγωνομετρικών συναρτήσεων. Οι τριγωνομετρικές συναρτήσεις δέχονται ως ορίσματα γωνίες κύκλων σε ακτίνια (radians), οπότε αν οι διαθέσιμες τιμές είναι σε μοίρες (degrees) τότε θα πρέπει πρώτα να μετατραπούν. Η μετατροπή γίνεται με βάση την εξίσωση 1𝜋 = 180∘, από όπου προκύπτει ότι η μετατροπή μοιρών σε ακτίνια γίνεται πολλαπλασιάζοντας τις μοίρες επί 0.0174532925, ενώ η μετατροπή ακτινίων σε μοίρες γίνεται πολλαπλασιάζοντας τα ακτίνια επί 57.2957795. Ο κώδικας 17.16 είναι ένα παράδειγμα χρήσης τριγωνομετρικών συναρτήσεων.

Κώδικας 17.16: ch17_p16.c - εμφάνιση τιμών ημιτόνου, συνημιτόνου και εφαπτομένης για τις γωνίες: 𝜋, 𝜋/2 , 𝜋/3 , 𝜋/4 , 𝜋/6 .
#include <math.h>
#include <stdio.h>

int main(void) {
  double pi = acos(-1);
  double a[] = {pi, pi / 2, pi / 3, pi / 4, pi / 6};
  for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
    printf("%lf(rad)=%3.0lf(deg): sin=%+lf, cos=%+lf, tan=%+lf\n", a[i],
           a[i] * 57.2957795, sin(a[i]), cos(a[i]), tan(a[i]));
  }
  return 0;
}

Ακολουθεί η έξοδος του προγράμματος:

3.141593(rad)=180(deg): sin=+0.000000, cos=‐1.000000, tan=‐0.000000
1.570796(rad)= 90(deg): sin=+1.000000, cos=+0.000000, tan=+16331239353195370.000000
1.047198(rad)= 60(deg): sin=+0.866025, cos=+0.500000, tan=+1.732051
0.785398(rad)= 45(deg): sin=+0.707107, cos=+0.707107, tan=+1.000000
0.523599(rad)= 30(deg): sin=+0.500000, cos=+0.866025, tan=+0.577350

Στη γραμμή 5, η τιμή του 𝜋 λαμβάνεται με το τόξο συνημιτόνου του -1. Η κλήση acos(‐1) επιστρέφει σε ακτίνια τη γωνία που το συνημίτονό της είναι -1, δηλαδή τη γωνία 𝜋. Η τιμή 𝜋 θα μπορούσε επίσης να ληφθεί με την έκφραση 4.0 * atan(1.0), καθώς η εφαπτομένη των 𝜋/4 = 45∘ είναι 1.0, ή με τη σταθερά M_PI της math.h (προστέθηκε με το πρότυπο C99). Ένα ακόμα σημείο με ενδιαφέρον στα αποτελέσματα είναι ότι η τιμή της εφαπτομένης που εμφανίζεται για τις 𝜋/2 = 90∘ είναι μια πολύ μεγάλη τιμή. Τυπικά, θα έπρεπε να υπήρχε κάποιος έλεγχος ξεχωριστά για την περίπτωση αυτή και όχι να εμφανίζεται μια αυθαίρετα μεγάλη τιμή. Ας σημειωθεί ότι καθώς η γωνία 𝜋/2 προσεγγίζεται από αριστερά (δηλαδή από μικρότερες τιμές), η τιμή της εφαπτομένης προσεγγίζει το -∞, ενώ όταν αυτό συμβαίνει από δεξιά (δηλαδή από μεγαλύτερες τιμές) η τιμή της εφαπτομένης προσεγγίζει το +∞. Αυτό φαίνεται στον κώδικα 17.17.

Κώδικας 17.17: ch17_p17.c - τιμές της εφαπτομένης καθώς η τιμή μιας γωνίας προσεγγίζει από δεξιά και από αριστερά τις 90∘.
#include <math.h>
#include <stdio.h>

int main(void) {
  double pi = acos(-1);
  double a[] = {-0.1,    -0.01,  -0.001, -0.0001, -0.00001,
                0.00001, 0.0001, 0.001,  0.01,    0.1};
  for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
    printf("%lf(rad)=%lf(deg): tan=%+.5lf\n", pi / 2 + a[i],
           (pi / 2 + a[i]) * 57.2957795, tan(pi / 2 + a[i]));
  }
  return 0;
}

Ακολουθούν τα αποτελέσματα εκτέλεσης του κώδικα:

1.470796(rad)=84.270422(deg): tan=+9.96664
1.560796(rad)=89.427042(deg): tan=+99.99667
1.569796(rad)=89.942704(deg): tan=+999.99967
1.570696(rad)=89.994270(deg): tan=+9999.99997
1.570786(rad)=89.999427(deg): tan=+100000.00000
1.570806(rad)=90.000573(deg): tan=‐100000.00000
1.570896(rad)=90.005730(deg): tan=‐9999.99997
1.571796(rad)=90.057296(deg): tan=‐999.99967
1.580796(rad)=90.572958(deg): tan=‐99.99667
1.670796(rad)=95.729578(deg): tan=‐9.96664

Οι τριγωνομετρικές συναρτήσεις τυπικά δέχονται ορίσματα double και επιστρέφουν τιμές double. Από το πρότυπο C99 και μετά υπάρχουν οι τριγωνομετρικές συναρτήσεις με ονόματα που τελειώνουν σε f και σε l, όπως για παράδειγμα οι sinf(), sinl(), που δέχονται ως ορίσματα, αλλά και επιστρέφουν float και long double τιμές αντίστοιχα, αντί για double. Επίσης, με το πρότυπο C99 προστέθηκε το tgmath.h που ορίζει μακροεντολές όπως η sin που παρέχουν γενερική (generic) πρόσβαση στις τριγωνομετρικές συναρτήσεις, καλώντας την κατάλληλη συνάρτηση ανάλογα με τον τύπο των ορισμάτων. Ο κώδικας 17.18 αποτελεί ένα παράδειγμα χρήσης αυτών των συναρτήσεων.

Κώδικας 17.18: ch17_p18.c - χρήση της μακροεντολής sin για κλήση της κατάλληλης συνάρτησης με βάση τον τύπο του ορίσματος.
#include <math.h>
#include <stdio.h>
#include <tgmath.h>

int main(void) {
  double angle_in_deg = 65.155; // γωνία σε μοίρες
  double angle_in_rad =
      angle_in_deg * acos(-1.0) / 180.0; // γωνία σε ακτίνια (double)
  float angle_in_rad_f =
      (float)angle_in_deg * acos(-1.0F) / 180.0F; // γωνία σε ακτίνια (float)
  long double angle_in_rad_Lf = (long double)angle_in_deg * acos(-1.0L) /
                                180.0L; // γωνία σε ακτίνια (long double)
  // χρήση macro sin από το <tgmath.h>
  printf("sinf(%.9f) using macro=%.9f\n", angle_in_rad_f, sin(angle_in_rad_f));
  printf("sin(%.17lf) using macro=%.17lf\n", angle_in_rad, sin(angle_in_rad));
  printf("sinl(%.21Lf) using macro=%.21Lf\n", angle_in_rad_Lf,
         sin(angle_in_rad_Lf));
  return 0;
}

Η έξοδος του προγράμματος είναι η ακόλουθη:

sinf(1.137169242) using macro=0.907447755
sin(1.13716927413690527) using macro=0.90744776169021168
sinl(1.137169274136905272599) using macro=0.907447761690211684993

Στις γραμμές 14-17 εκτελείται τρεις φορές η μακροεντολή sin και με βάση το όρισμα που δέχεται, καλεί την κατάλληλη συνάρτηση ανάμεσα στις sinf(), sin() και sinl().

17.1.5 time.h

Το αρχείο επικεφαλίδας time.h ορίζει διάφορους τύπους δεδομένων σχετικούς με τον χρόνο, όπως τον time_t, τον clock_t και τον struct tm, που θα παρουσιαστούν στη συνέχεια μαζί με συναρτήσεις που τους χρησιμοποιούν και παραδείγματα.
Ο τύπος δεδομένων time_t σε συστήματα που υποστηρίζουν το POSIX, είναι ένας ακέραιος τύπος με εύρος τιμών ικανό να αναπαραστήσει το πλήθος δευτερολέπτων που έχουν παρέλθει από το λεγόμενο epoch που είναι η 1η Ιανουαρίου του 1970 στις 00:00:00 UTC (Coordinated Universal Time). Η συνάρτηση time() μπορεί να χρησιμοποιηθεί για να επιστραφεί το πλήθος των δευτερολέπτων της τρέχουσας χρονικής στιγμής από το epoch, ενώ οι συναρτήσεις difftime() και ctime() χρησιμοποιούνται για τον υπολογισμό της απόστασης σε δευτερόλεπτα ανάμεσα σε δύο τιμές τύπου time_t και για την εμφάνιση τιμών τύπου time_t ως αλφαριθμητικά χρονικών στιγμών (ημερομηνία και ώρα) αντίστοιχα. Στο ακόλουθο παράδειγμα (κώδικας 17.19) χρησιμοποιούνται οι συναρτήσεις που αναφέρθηκαν και η συνάρτηση sleep() που προκαλεί παύση της εκτέλεσης για ένα διάστημα που δίνεται σε δευτερόλεπτα. Η συνάρτηση sleep() ορίζεται στο αρχείο επικεφαλίδας unistd.h.

Κώδικας 17.19: ch17_p19.c - χρήση της συνάρτησης sleep() για προσωρινή παύση εκτέλεσης.
#include <stdio.h>
#include <time.h>
#include <unistd.h>

int main(void) {
  time_t t1 = time(NULL); // τρέχουσα χρονική στιγμή
  char *s_t1 = ctime(&t1);
  printf("Time1: %s", s_t1);
  sleep(2); // παύση εκτέλεσης για 2 δευτερόλεπτα
  time_t t2 = time(NULL); // τρέχουσα χρονική στιγμή
  char *s_t2 = ctime(&t2);
  printf("Time2: %s", s_t2);
  int time_elapsed = difftime(t2, t1);
  printf("Time gap between prints: %d seconds\n", time_elapsed);
  return 0;
}

Ακολουθεί ένα αποτέλεσμα εκτέλεσης:

Time1: Sun Jul 23 16:58:01 2023
Time2: Sun Jul 23 16:58:03 2023
Time gap between prints: 2 seconds

Ο τύπος clock_t μπορεί να χρησιμοποιηθεί για να αποθηκεύσει μια τιμή ανάλογη του πλήθους των χτύπων (ticks) του ρολογιού από την αρχή εκτέλεσης του προγράμματος μέχρι το σημείο όπου καλείται η συνάρτηση clock(). Ως χτύπος ρολογιού μπορεί να θεωρηθεί ότι είναι ο χρόνος που απαιτείται για να εκτελεστεί μια μικροεντολή στην ΚΜΕ (Κεντρική Μονάδα Επεξεργασίας). Για να μετατραπεί η τιμή που επιστρέφει η clock() σε χρόνο διαιρείται με τη σταθερά CLOCKS_PER_SEC, οπότε επιστρέφεται μια δεκαδική τιμή που αναπαριστά δευτερόλεπτα. Ο τύπος clock_t χρησιμοποιείται συχνά για να μετρήσει τον χρόνο εκτέλεσης κώδικα ανάμεσα σε δύο σημεία ενδιαφέροντος στον κώδικα. Ένα σχετικό παράδειγμα παρουσιάζεται στον κώδικα 17.20 που ακολουθεί:

Κώδικας 17.20: ch17_p20.c - χρονομέτρηση κώδικα.
#include <stdio.h>
#include <time.h>

int main(void) {
  clock_t start_time, end_time;
  double elapsed_time;

  start_time = clock();
  long long sum = 0;
  for (int i = 1; i <= 100000000; i++) {
    sum += i;
  }
  end_time = clock();

  elapsed_time = (double)(end_time - start_time) / CLOCKS_PER_SEC;
  printf("CLOCKS_PER_SEC=%lu, start=%lu, end=%lu\n", CLOCKS_PER_SEC, start_time,
         end_time);
  printf("Result=%lld\n", sum);
  printf("Elapsed time=%.6f seconds\n", elapsed_time);
  return 0;
}

Ακολουθεί ένα αποτέλεσμα εκτέλεσης. Ας σημειωθεί ότι η σταθερά CLOCKS_PER_SEC έχει την προκαθορισμένη τιμή 1000000, ανεξάρτητα του υλικού του συστήματος που εκτελεί τον κώδικα.

CLOCKS_PER_SEC=1000000, start=3664, end=122949
Result=5000000050000000
Elapsed time=0.119285 seconds

Ένας άλλος τύπος δεδομένων του time.h, που επιτρέπει τον χειρισμό ημερομηνιών και ωρών, είναι η δομή struct tm που παρατίθεται στη συνέχεια:

struct tm {
int tm_sec; // δευτερόλεπτα , 0‐59
int tm_min; // λεπτά, 0‐59
int tm_hour; // ώρες, 0‐23
int tm_mday; // ημέρα μήνα, 1‐31
int tm_mon; // μήνας, 0‐11
int tm_year; // πλήθος ετών από το 1900
int tm_wday; // ημέρα εβδομάδας από την Κυριακή , 0‐6
int tm_yday; // πλήθος ημερών από 1η Ιανουαρίου , 0‐365
int tm_isdst; // > 0 για θερινή ώρα, 0 για χειμερινή , < 0 για μη διαθέσιμη πληροφορία
};

Υπάρχουν διάφορες συναρτήσεις που χρησιμοποιούν το struct tm, όπως η gmtime(), η localtime(), η asctime() και η strftime(). Η gmtime() μετατρέπει μια τιμή time_t σε struct tm, όπως και η locatime() με τη διαφορά ότι η πρώτη κάνει τη μετατροπή σε UTC χρόνο, ενώ η δεύτερη σε τοπικό χρόνο. Ο κώδικας 17.21 αποτελεί ένα παράδειγμα όπου λαμβάνεται η τρέχουσα ημέρα και ώρα κατά την εκτέλεση ως time_t και μετατρέπεται με τη gmtime() σε struct tm. Στη συνέχεια μετατρέπεται με την asctime() η struct tm σε αλφαριθμητικό ημερομηνίας και ώρας προκειμένου να εμφανιστεί. Μετά, προστίθεται 1 ημέρα και 10 ώρες στη δομή και μετατρέπεται σε time_t, πριν τελικά μετατραπεί ξανά με τη gmtime() σε struct tm. Αυτή η διπλή μετατροπή γίνεται προκειμένου να περιοριστούν οι τιμές των επιμέρους πεδίων του struct tm σε έγκυρες τιμές (π.χ. να μην υπάρχει τιμή μεγαλύτερη του 23 για τις ώρες). Τέλος, χρησιμοποιείται η συνάρτηση strftime() για να τοποθετηθεί σε έναν πίνακα χαρακτήρων το αλφαριθμητικό εξόδου με την ημερομηνία και ώρα. Η strftime() χρησιμοποιεί ως τρίτο στη σειρά όρισμα ένα αλφαριθμητικό που καθορίζει με ειδικούς κωδικούς μορφοποίησης(1) τη μορφή που θα έχει το αποτέλεσμα.

  1. https://en.cppreference.com/w/c/chrono/strftime
Κώδικας 17.21: ch17_p21.c - προσθήκη ενός χρονικού διαστήματος σε μια ημερομηνία και εμφάνιση της νέας ημερομηνίας.
#include <stdio.h>
#include <time.h>

int main(void) {
  time_t now = time(NULL);
  struct tm *t = gmtime(&now);
  printf("UTC time=%s", asctime(t));
  // 1 ημέρα + 10 ώρες μετά
  t->tm_mday += 1;
  t->tm_hour = t->tm_hour + 10;
  // μετατροπή σε time_t για κανονικοποίηση ημερομηνίας
  time_t future_time = timegm(t);
  t = gmtime(&future_time);
  char s[100];
  strftime(s, sizeof s, "%A %c", t);
  printf("UTC time(+1day, 10hours)=%s\n", s);
  return 0;
}

Ακολουθεί ένα αποτέλεσμα εκτέλεσης. Το ίδιο παράδειγμα θα μπορούσε να χρησιμοποιεί την τοπική ώρα αν όλες οι κλήσεις της gmtime() είχαν αντικατασταθεί με κλήσεις της localtime().

UTC time=Sun Jul 23 16:00:41 2023
UTC time(+1day, 10hours)=Tuesday Tue Jul 25 02:00:41 2023

17.2 Εξωτερικές βιβλιοθήκες της C

Ένα μεγάλο πλεονέκτημα της C είναι η πληθώρα βιβλιοθηκών που υπάρχουν για την εξυπηρέτηση διαφόρων αναγκών όπως δημιουργία γραφικών διεπαφών (π.χ. GTK+, IUP), προχωρημένες δυνατότητες μαθηματικών (π.χ. GSL, BLAS, GMP), αλληλεπίδραση με βάσεις δεδομένων (π.χ. SQLite), γραφικά (π.χ. SDL, SIGIL), επικοινωνίες (π.χ. libcurl, ZeroMQ) και άλλα. Μια λίστα με εξωτερικές βιβλιοθήκες ανοικτού κώδικα της C μπορεί να εντοπιστεί στο 1. Επίσης, σελίδες τύπου awesome(1) όπως η 2 περιέχουν συνδέσμους προς βιβλιοθήκες και άλλα ενδιαφέροντα θέματα σχετικά με τη C, οργανωμένα σε κατηγορίες.

  1. https://github.com/topics/awesome-list

Στη συνέχεια θα παρουσιαστεί ένα παράδειγμα εγκατάστασης και χρήσης της εξωτερικής βιβλιοθήκης GSL. Υπάρχουν διάφοροι τρόποι για την εγκατάσταση βιβλιοθηκών της C, που εξαρτώνται από το λειτουργικό σύστημα και την ίδια τη βιβλιοθήκη. Για παράδειγμα υπάρχουν βιβλιοθήκες που είναι “header only” που σημαίνει ότι αρκεί να μεταφορτωθεί ένα αρχείο επικεφαλίδας και στη συνέχεια απλά να γίνει include έτσι ώστε να χρησιμοποιηθεί η βιβλιοθήκη στον κώδικα. Ωστόσο, συχνότερα, οι βιβλιοθήκες πρέπει να εγκαθίστανται προκειμένου να χρησιμοποιηθούν. Στο Linux υπάρχουν οι λεγόμενοι διαχειριστές πακέτων (package managers), που επιτρέπουν την εγκατάσταση βιβλιοθηκών και άλλων λογισμικών. Έτσι, η βιβλιοθήκη GSL μπορεί να εγκατασταθεί, για παράδειγμα, σε μια διανομή Ubuntu, με την ακόλουθη εντολή:

$ sudo apt‐get install libgsl‐dev

Αυτό που συμβαίνει με μια τέτοια εντολή είναι ότι αντιγράφονται στον υπολογιστή όλα τα απαιτούμενα αρχεία της βιβλιοθήκης GSL, και το λειτουργικό σύστημα ενημερώνεται για τις θέσεις των αρχείων επικεφαλίδας και των δυαδικών αρχείων της βιβλιοθήκης. Η εγκατάσταση της βιβλιοθήκης σε MacOS μπορεί να γίνει με παρόμοιο τρόπο, εφόσον πρώτα εγκατασταθεί ένας διαχειριστής πακέτων όπως ο HomeBrew ή ο MacPorts. Στα Windows η εγκατάσταση δεν είναι το ίδιο εύκολη καθώς δεν διατίθεται επίσημος διαχειριστής πακέτων και οι λύσεις που προτείνονται, όπως το chocolatey, προσφέρουν δυνατότητα εγκατάστασης μόνο για κάποιες βιβλιοθήκες και λογισμικά. Για να γεφυρωθούν οι διαφορές στον τρόπο εγκατάστασης των εξωτερικών βιβλιοθηκών ανάμεσα στα διάφορα λειτουργικά συστήματα και τις εκδόσεις τους, μπορεί να χρησιμοποιηθεί το λογισμικό vcpkg (https://github.com/microsoft/vcpkg)
Το vcpkg είναι ένα σύστημα διαχείρισης εγκαταστάσεων βιβλιοθηκών για τη C και τη C++. Έχει αναπτυχθεί από τη Microsoft και επιτρέπει την αυτοματοποίηση της λήψης, της μεταγλώττισης και σύνδεσης (build), καθώς και της εγκατάστασης βιβλιοθηκών C και C++ σε συστήματα Windows, Linux και MacOS. Ο σκοπός του είναι να απλοποιήσει τη διαδικασία ανάπτυξης λογισμικού με αυτοματοποίηση της διαχείρισης εγκατάστασης βιβλιοθηκών.
Οι οδηγίες εγκατάστασης του vcpkg βρίσκονται στο https://github.com/microsoft/vcpkg. Από τη στιγμή που έχει εγκατασταθεί η εγκατάσταση των βιβλιοθηκών γίνεται με εντολές της μορφής:

$ vcpkg install [βιβλιοθήκη προς εγκατάσταση]

Για παράδειγμα, η βιβλιοθήκη GSL εγκαθίσταται με την εντολή:

$ vcpkg install gsl

Το vcpkg ανιχνεύει το υπολογιστικό σύστημα στο οποίο εκτελείται και εγκαθιστά την κατάλληλη έκδοση της βιβλιοθήκης. Το vcpkg χρησιμοποιεί τις λεγόμενες τριάδες (triplets) στη διαχείριση των διαφορετικών διαμορφώσεων κατά την εγκατάσταση των βιβλιοθηκών. Κάθε τριάδα αντιπροσωπεύει μια πλατφόρμα, αρχιτεκτονική και πρόσθετες ρυθμίσεις. Για παράδειγμα η τριάδα x64‐linux‐dynamic σημαίνει ότι η αρχιτεκτονική της πλατφόρμας είναι 64 bits, το λειτουργικό σύστημα είναι linux και θα δημιουργηθεί βιβλιοθήκη για δυναμική σύνδεση. Συνεπώς, η βιβλιοθήκη GSL θα μπορούσε να είχε εγκατασταθεί και με την ακόλουθη εντολή:

$ vcpkg install gsl:x64‐linux‐dynamic

Για να μπορεί να χρησιμοποιηθεί η βιβλιοθήκη GSL θα πρέπει να οριστούν κατά τη μεταγλώττιση και σύνδεση μέσω των διακοπτών ‐I και ‐L οι κατάλληλες διαδρομές προς τα αρχεία επικεφαλίδας και τα δυαδικά αρχεία της βιβλιοθήκης. Στο παράδειγμα του κώδικα 17.22 χρησιμοποιείται η δυνατότητα της GSL να δημιουργεί όλες τις μεταθέσεις (permutations) των στοιχείων ενός συνόλου.

Κώδικας 17.22: ch17_p22.c - παραγωγή όλων των μεταθέσεων ενός συνόλου αλφαριθμητικών, με την εξωτερική βιβλιοθήκη GSL.
#include <gsl/gsl_permutation.h>
#include <stdio.h>

void print_permutation(const gsl_permutation *p, char *names[], size_t size) {
  for (size_t i = 0; i < size; i++) {
    printf("%s ", names[gsl_permutation_get(p, i)]);
  }
  printf("\n");
}

int main(void) {
  char *names[] = {"Alice", "Bob", "Charlie"};
  size_t size = sizeof(names) / sizeof(names[0]);

  gsl_permutation *p = gsl_permutation_alloc(size);
  gsl_permutation_init(p);

  do {
    print_permutation(p, names, size);
  } while (gsl_permutation_next(p) == GSL_SUCCESS);

  gsl_permutation_free(p);
  return 0;
}

Η μεταγλώττιση του κώδικα θα γίνει με τις ακόλουθες εντολές μεταγλώττισης ανά περίπτωση. Σε Windows, υποθέτοντας ότι ο χρήστης του συστήματος έχει όνομα a_user και ότι το vcpkg έχει εγκατασταθεί στον κατάλογο c:\Users\a_user, η μεταγλώττιση θα πρέπει να γίνει με την ακόλουθη εντολή:

> gcc ch17_p22.c ‐IC:\Users\a_user\vcpkg\installed\x64‐windows\include
    ↪ ‐LC:\Users\a_user\vcpkg\installed\x64‐windows\lib ‐lgsl ‐lgslcblas ‐lm

Στα Windows θα πρέπει να τοποθετηθούν τα αρχεία gsl.dll και gslcblas.dll στον ίδιο κατάλογο με το εκτελέσιμο. Τα αρχεία αυτά έχουν δημιουργηθεί κατά την εγκατάσταση και βρίσκονται στη διαδρομή C:\Users\a_user\vcpkg\installed\x64‐windows\bin. Εναλλακτικά, αυτή η διαδρομή μπορεί να προστεθεί στο PATH, οπότε δεν θα απαιτούνταν η αντιγραφή των αρχείων gsl.dll και gslcblas.dll.
Σε Linux υποθέτοντας ότι ο χρήστης του συστήματος έχει όνομα a_user και ότι το vcpkg έχει εγκατασταθεί στον γονικό κατάλογο (/home/a_user), η μεταγλώττιση θα πρέπει να γίνει με την ακόλουθη εντολή:

$ gcc ch17_p22.c ‐I/home/a_user/vcpkg/installed/x64‐linux/include
    ↪ ‐L/home/a_user/vcpkg/installed/x64‐linux/lib ‐lgsl ‐lgslcblas ‐lm

Σε MacOS (αρχιτεκτονικής arm64) υποθέτοντας ότι ο χρήστης του συστήματος έχει όνομα a_user και ότι το vcpkg έχει εγκατασταθεί στον γονικό κατάλογο (/Users/a_user), η μεταγλώττιση θα πρέπει να γίνει με την ακόλουθη εντολή:

$ gcc ch17_p22.c ‐I/Users/a_user/vcpkg/installed/arm64‐osx/include/
    ↪ ‐L/Users/a_user/vcpkg/installed/arm64‐osx/lib ‐lgsl ‐lgslcblas ‐lm

Τα αποτελέσματα εκτέλεσης θα είναι και στις 3 περιπτώσεις τα ακόλουθα:

Alice Bob Charlie
Alice Charlie Bob
Bob Alice Charlie
Bob Charlie Alice
Charlie Alice Bob
Charlie Bob Alice
Αξίζει να σημειωθεί ότι το vcpkg συνεργάζεται καλά με το CMAKE καθώς και με το pkg-config. Η ανάπτυξη των συγκεκριμένων δυνατοτήτων του vcpkg είναι εκτός του αντικειμένου του παρόντος συγγράμματος.

17.3 Ασκήσεις

Άσκηση 1
Γράψτε ένα πρόγραμμα που ο χρήστης θα προσπαθεί να εντοπίσει, με τον μικρότερο αριθμό προσπαθειών, έναν ακέραιο αριθμό, που θα έχει δημιουργηθεί τυχαία σε ένα εύρος τιμών (π.χ. 1 έως 100). Το πρόγραμμα θα ζητά από τον χρήστη να μαντέψει τον αριθμό και σε περίπτωση αποτυχίας θα εμφανίζει μήνυμα σχετικά με το εάν ο αριθμός που επιλέχθηκε είναι μικρότερος ή μεγαλύτερος από τον αριθμό που αναζητείται. Όταν ο χρήστης πετύχει τον αριθμό, το πρόγραμμα θα σταματά και θα εμφανίζει το πλήθος των προσπαθειών που χρειάστηκαν για τον εντοπισμό του.

Άσκηση 2
Γράψτε ένα πρόγραμμα που να ζητά από τον χρήστη να εισάγει δύο ημερομηνίες, σε μορφή έτους, μήνα, ημέρας. Το πρόγραμμα να μετατρέπει τις ημερομηνίες σε time_t, να υπολογίζει τη διαφορά τους σε ημέρες και να την εμφανίζει.

Άσκηση 3
Γράψτε ένα πρόγραμμα που να υπολογίζει χρησιμοποιώντας επανάληψη πόσες φορές χρειάζεται να υποδιπλασιαστεί μια ποσότητα έτσι ώστε να γίνει ίση ή μικρότερη από μια άλλη ποσότητα. Υπολογίστε την ίδια τιμή με χρήση λογαριθμικής συνάρτησης από το math.h.

Άσκηση 4
Δημιουργήστε δύο ίσου μεγέθους πίνακες με τυχαίες τιμές, στο εύρος τιμών από 1 μέχρι 100, που ο χρήστης θα δίνει το πλήθος των στοιχείων τους. Χρησιμοποιήστε τη βιβλιοθήκη GSL για να επιτύχετε τον υπολογισμό του εσωτερικού γινομένου των δύο πινάκων.


  1. cppreference.com: A list of open source C libraries. https://en.cppreference.com/w/c/links/libs. [Online; accessed 2023-July-12]. 

  2. Awesome-c: Curated list of awesome lists. https://project-awesome.org/inputsh/awesome-c.[Online; accessed 2023-July-12].