void *malloc(unsigned int size)
malloc με παράμετρο έναν ακέραιο size,1 αυτή δεσμεύει στο σωρό size συνεχόμενα bytes και επιστρέφει στο όνομά της ένα δείκτη στο πρώτο απ’ αυτά.malloc δείχνει σε τύπο void, δηλαδή στον κενό τύπο, αφού δεν την αφορά τι τύπου δεδομένα θα φυλάξουμε στη μνήμη που δέσμευσε.malloc δεν μπορέσει να δεσμεύσει τη μνήμη που της ζητήθηκε, επιστρέφει στο όνομά της τον κενό δείκτη NULL. Πάντα, όταν καλούμε την malloc, πρέπει να ελέγχουμε αν μας επέστρεψε δείκτη διάφορο του NULL και μετά να προχωρήσουμε.malloc να δεσμεύσει το δίνουμε μέσω του τελεστή sizeof.Η έκφραση sizeof(⟨τύπος⟩) έχει σαν τιμή το πλήθος των bytes που απαιτούνται στη συγκεκριμένη υλοποίηση της C για να φυλαχθούν δεδομένα που ο τύπος τους είναι ⟨τύπος⟩. Για παράδειγμα, με το παρακάτω τμήμα προγράμματος
int n, *p;
................ /* Compute n */
p = malloc(n * sizeof(int));
if (p == NULL) {
printf("Sorry, cannot allocate memory\n");
return -1;
}
................ /* Handle data starting at p */
δεσμεύουμε δυναμικά χώρο στο σωρό για να φυλαχθούν n ακέραιοι.
Στην προ ANSI C εποχή, ο δείκτης που επέστρεφε η malloc (τύπου void *) έπρεπε πρώτα να προσαρμοσθεί στον κατάλληλο τύπο δείκτη και μετά να ανατεθεί σε μεταβλητή. Δηλαδή την κλήση της malloc στο παραπάνω τμήμα προγράμματος θα την γράφαμε σαν:
p = (int *) malloc(n * sizeof(int));
Πλέον, κάτι τέτοιο δεν είναι απαραίτητο και, για την ακρίβεια, δεν συνίσταται.
sizeof μπορεί να χρησιμοποιηθεί είτε σαν sizeof ⟨παράσταση⟩, είτε σαν sizeof(⟨παράσταση⟩), οπότε αυτή η έκφραση έχει σαν τιμή το πλήθος των bytes που απαιτούνται για να φυλαχθεί η ⟨παράσταση⟩, π.χ. το sizeof buf είναι 20, αν έχουμε ορίσει char buf[20].
void free(void *p)
free είναι ένας δείκτης που μας είχε επιστρέψει κάποια malloc.malloc, υπάρχουν και δύο άλλες συναρτήσεις για δυναμική δέσμευση μνήμης (η realloc και η calloc). Είναι πιο σπάνια χρησιμοποιούμενες από την malloc. Μέσω της εντολής man, μπορεί να μάθει κανείς περισσότερες πληροφορίες γι’ αυτές.Όταν σ’ ένα πρόγραμμα χρησιμοποιούμε συναρτήσεις για δυναμική δέσμευση και αποδέσμευση μνήμης, πρέπει να έχουμε συμπεριλάβει στην αρχή το αρχείο επικεφαλίδας stdlib.h. Δηλαδή:
#include <stdlib.h>
Σε σχέση με τα προβλήματα που δημιουργούνται όταν ορίζουμε με στατικό τρόπο πίνακες για να διαχειριστούμε ένα σύνολο από δεδομένα, αντί να κάνουμε δυναμική δέσμευση μνήμης, θα πρέπει να αναφέρουμε ότι στην C υπάρχει και η δυνατότητα να ορίσουμε πίνακες με μεταβλητή διάσταση, για παράδειγμα:
int array[n];
Φυσικά, πριν από τη δήλωση αυτή, στην ακέραια μεταβλητή n πρέπει να έχουμε δώσει τιμή.
malloc και δεικτών, παρά με πίνακες που η διάστασή τους είναι παραμετρική.char). Ο χαρακτήρας '\0', σαν στοιχείο του πίνακα, δείχνει το τέλος της συμβολοσειράς και είναι απολύτως απαραίτητος για να μπορούν να λειτουργήσουν οι συναρτήσεις βιβλιοθήκης για το χειρισμό συμβολοσειρών.char *) που δείχνει στον πρώτο χαρακτήρα της συμβολοσειράς.Μέσα σ’ ένα πρόγραμμα, μπορούμε μ’ ένα σύντομο τρόπο να αναφερθούμε σε μία συμβολοσειρά για την οποία γνωρίζουμε τους χαρακτήρες που περιέχει, περικλείοντας αυτούς τους χαρακτήρες μέσα σε " (χωρίς το '\0'). Παράδειγμα:
char *mess = "Hello world!\n";
Εκτός από την αρχικοποίηση ενός δείκτη σε char με μία συμβολοσειρά, όπως κάναμε με την εντολή
char *mess = "Hello world!\n";
μπορούμε να αρχικοποιήσουμε και ένα πίνακα χαρακτήρων με μία συμβολοσειρά, ως εξής:
char arrmess[] = "How are you?";
Δεν υπάρχει λόγος να ορίσουμε διάσταση για τον πίνακα arrmess, γιατί αυτή υπολογίζεται αυτόματα από το πλήθος των χαρακτήρων της συμβολοσειράς (συν έναν, λόγω του '\0').
mess είναι δείκτης που αρχικοποιήθηκε με τη συμβολοσειρά που θέλουμε, στη συνέχεια, όμως, μπορεί να αλλάξει τιμή, αν χρειάζεται, ενώ το όνομα του πίνακα arrmess, που επίσης αρχικοποιήθηκε με μία συμβολοσειρά, λειτουργεί, φυσικά, και ως δείκτης, μόνο που δεν επιτρέπεται να μεταβληθεί."Hello world!\n" που δείχνει αρχικά ο δείκτης mess, παρότι μπορούμε να αλλάξουμε την τιμή του ίδιου του δείκτη, ενώ μπορούμε να αλλάξουμε τους χαρακτήρες της συμβολοσειράς "How are you?", παρότι δεν μπορούμε να αλλάξουμε το arrmess.Όταν χρησιμοποιούμε σ’ ένα πρόγραμμα συναρτήσεις για διαχείριση συμβολοσειρών, πρέπει να έχουμε συμπεριλάβει στην αρχή το αρχείο επικεφαλίδας string.h. Δηλαδή:
#include <string.h>
unsigned int strlen(const char *s)
char *strcpy(char *s1, const char *s2)
int strcmp(const char *s1, const char *s2)
char *strcat(char *s1, const char *s2)
strlen επιστρέφει το μήκος της συμβολοσειράς s (χωρίς το τελικό '\0'). 2strcpy αντιγράφει τη συμβολοσειρά s2, μέχρι και το τελικό '\0', στη συμβολοσειρά s1, την οποία επιστρέφει και στο όνομά της.strcmp συγκρίνει τις συμβολοσειρές s1 και s2 byte προς byte και επιστρέφει έναν ακέραιο θετικό, μηδέν ή αρνητικό, ανάλογα αν η συμβολοσειρά s1 ακολουθεί (αλφαβητικά), ταυτίζεται ή προηγείται της συμβολοσειράς s2, αντίστοιχα. Η σύγκριση γίνεται με βάση τους ASCII κωδικούς των χαρακτήρων των συμβολοσειρών.strcat προσαρτά ένα αντίγραφο της συμβολοσειράς s2 στο τέλος της s1, διαγράφοντας πρώτα το '\0' της s1, και επιστρέφει το αποτέλεσμα και στο όνομά της.const για να επισημανθεί ότι οι αντίστοιχες συμβολοσειρές δεν θα μεταβληθούν από τις συναρτήσεις.strcpy, strcat) δεν αναλαμβάνουν και τη δέσμευση μνήμης για τη φύλαξή τους. Είναι ευθύνη της καλούσας συνάρτησης να το κάνει αυτό.strncpy, strchr, strstr, κλπ. Η εντολή man είναι ένα μέσο για να μάθει κανείς περισσότερες πληροφορίες γι’ αυτές.Κάποιες πιθανές υλοποιήσεις: 3
char *strcpy(char *s1, const char *s2)
{ char *orig_s1 = s1;
while (*s1++ = *s2++);
return orig_s1; }
int strcmp(const char *s1, const char *s2)
{ for ( ; *s1 == *s2 ; s1++, s2++)
if (! *s1)
return 0;
return *s1 - *s2; }
Παράδειγμα:
int i, j, *px[3], **ppx, s = 0;
for (i=0 ; i < 3 ; i++) {
px[i] = malloc((i+2) * sizeof(int));
if (px[i] == NULL)
return -1; }
for (i=0 ; i < 3 ; i++)
for (j=0 ; j < i+2 ; j++)
*(px[i]+j) = i*j;
ppx = px;
for (i=0 ; i < 3 ; i++) {
for (j=0 ; j < i+2 ; j++)
s += *(*ppx)++;
ppx++; }
Ποια θα είναι η τιμή της μεταβλητής s μετά την εκτέλεση των παραπάνω εντολών; 4
main.main με δύο τυπικές παραμέτρους, μία ακέραια, συνήθως με όνομα argc, και μία πίνακα δεικτών σε χαρακτήρες (ή δείκτη σε δείκτη σε χαρακτήρες), συνήθως με όνομα argv, μέσα στη συνάρτηση, μπορούμε να έχουμε πρόσβαση στα ορίσματα με τα οποία κλήθηκε το πρόγραμμα στη γραμμή εντολής.Αν ένα εκτελέσιμο πρόγραμμα myprog κληθεί σαν
% ./myprog first 241 third
και η συνάρτηση main του προγράμματος έχει ορισθεί σαν
int main(int argc, char *argv[])
τότε, μέσα στην main, η μεταβλητή argc έχει την τιμή 4 (όσα τα ορίσματα συν το όνομα του προγράμματος) και οι δείκτες argv[0], argv[1], argv[2] και argv[3] δείχνουν στην αρχή των συμβολοσειρών "./myprog", "first", "241" και "third", αντίστοιχα.
argv[argc] (στο προηγούμενο παράδειγμα ο argv[4]) είναι ο κενός δείκτης, NULL.main μπορεί να ορισθεί είτε σαν char *argv[] είτε σαν char **argv, ισοδύναμα.
int atoi(const char *s)
atoi υπολογίζει την τιμή της (αριθμητικής) συμβολοσειράς s και την επιστρέφει στο όνομά της.atoi(argv[2]) θα επέστρεφε την τιμή 241.Όταν χρησιμοποιούμε την atoi, πρέπει να κάνουμε και:
#include <stdlib.h>
Με τη δήλωση
int matrix[10][8];
ορίζουμε ένα δισδιάστατο πίνακα με όνομα matrix με 10 γραμμές και 8 στήλες.
matrix[i][j] είναι αυτό που βρίσκεται στην i γραμμή και στην j στήλη. 5matrix, τότε το matrix[i] (ισοδύναμα το *(matrix+i)) είναι ένας δείκτης στο πρώτο στοιχείο της i γραμμής του πίνακα. Οπότε, το *(matrix[i]+j) (ισοδύναμα το *(*(matrix+i)+j)) δεν είναι άλλο από το στοιχείο matrix[i][j] του πίνακα.p, που δείχνει σε τύπο ό,τι και ο τύπος των στοιχείων του πίνακα, τη διεύθυνση του πρώτου στοιχείου του πίνακα (π.χ. matrix[0][0]), τότε μπορούμε να προσπελάσουμε τα περιεχόμενα του πίνακα, τη μία γραμμή μετά την άλλη, με παραστάσεις της μορφής *(p+i).*matrix είναι το ίδιο με το matrix[0], δηλαδή ένας δείκτης στο πρώτο στοιχείο της πρώτης γραμμής (αυτής με i=0) του πίνακα, μπορούμε να προσπελάσουμε κατά γραμμές τα περιεχόμενα του πίνακα και με παραστάσεις της μορφής *(*matrix+i).Αν θέλουμε να περάσουμε σε μία συνάρτηση έναν πολυδιάστατο πίνακα, τότε στην κλήση της συνάρτησης δίνουμε το όνομά του και στον ορισμό της δηλώνουμε, μαζί με το όνομά του, και όλες τις διαστάσεις του, εκτός της πρώτης, η οποία δεν είναι υποχρεωτική. Παράδειγμα:
int main(void)
{ int matr[16][12];
....
myfun(matr);
.... }
void myfun(int matr[][12])
{ .... }
Αν σ’ ένα πρόγραμμα χρειάζεται να φυλάξουμε δεδομένα σ’ ένα δισδιάστατο πίνακα, αγνώστου, κατ’ αρχήν, μεγέθους, αντί να δηλώσουμε κάτι σαν
int x[10000][10000];
μπορούμε να ορίσουμε ένα δείκτη
int **px;
και μετά να κάνουμε:
px = malloc(N * sizeof(int *));
if (px == NULL)
return -1;
for (i=0 ; i < N ; i++) {
*(px+i) = malloc(M * sizeof(int));
if (*(px+i) == NULL)
return -1; }
Έτσι, όταν τα N και M γίνουν γνωστά κατά τη φάση εκτέλεσης του προγράμματος, θα δεσμεύσουμε όση ακριβώς μνήμη χρειαζόμαστε. Αφού τελειώσουμε ό,τι έχουμε να κάνουμε, μετά πρέπει να αποδεσμεύσουμε τη μνήμη ως εξής:
for (i=0 ; i < N ; i++)
free(*(px+i));
free(px);
Για να περάσουμε σε μία συνάρτηση ένα πολυδιάστατο πίνακα ορισμένο δυναμικά, αρκεί να την καλέσουμε με παράμετρο τον δείκτη μέσω του οποίου δεσμεύτηκε η μνήμη, π.χ. fun(px);, και να έχουμε ορίσει τη συνάρτηση με τυπική παράμετρο ένα δείκτη του κατάλληλου τύπου. Δηλαδή:
void fun(int **px) { ....
Παραδείγματα:
int x[7] = {5, 3, -7, 12, 0, -4, 125};
float r[] = {1.2, -4.35, 0.62e-17};
int matrix[3][5] = {
{22, 1, -3, 8, 7},
{-2, 0, 24, 7, -10},
{76, 54, 12, -9, 8}
};
char arr[][2] = {{'a','b'},{'c','d'},{'e','f'}};
char mess[] = "a dog";
char buff[] = {'a',' ','d','o','g','\0'};
char *str[] = {"Hello", "world", "of", "C"};
*, μπορούμε να αναφερθούμε σε συγκεκριμένες συναρτήσεις, μπορούμε να αναθέτουμε τις τιμές αυτών των δεικτών σε άλλους συμβατούς δείκτες, μπορούμε να τους περνάμε σαν παραμέτρους σε συναρτήσεις κλπ. Δεν έχει νόημα όμως να τους μεταβάλλουμε (π.χ. p++).Παράδειγμα:
int (*funvar)(char *);
Το funvar είναι ένας δείκτης σε συνάρτηση που επιστρέφει int και έχει μία τυπική παράμετρο τύπου char *.
Τι διαφορά θα είχε αν γράφαμε το παρακάτω; 6
int *funvar(char *);
/* File: cmdlineargs.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int checkint(char *);
void reverse(char *);
int main(int argc, char *argv[])
{ int sum = 0; /* Initialize sum of integers in command line */
while (--argc) { /* Loop while arguments are present */
if(checkint(*++argv)) /* Is argument an integer? */
sum += atoi(*argv); /* Then, sum it into the accumulator */
else {
reverse(argv[0]); /* Else, reverse argument and print it */
printf("Reversed argument: %s\n", argv[0]);
}
} /* Finally, print sum of integer arguments */
printf("\nSum of integers given is: %d\n", sum);
return 0;
}
int checkint(char *s)
{ char *start;
while(*s == ' ' || *s == '\t') s++; /* Eat all whitespace */
if (*s == '-' || *s == '+') s++; /* Eat one sign */
start = s; /* Mark the begining of numbers */
while(*s >= '0' && *s <= '9') s++; /* Eat all numbers */
return (*s == '\0' && start != s);
/* Return if there where numbers and nothing else */
}
void reverse(char *s)
{ char c;
int i, j;
for (i=0, j=strlen(s)-1 ; i < j ; i++, j--) {
c = s[i]; /* Visit string from start and end concurrently */
s[i] = s[j]; /* and exchange characters in symmetric */
s[j] = c; /* positions until middle is reached */
}
}
% gcc -o cmdlineargs cmdlineargs.c
% ./cmdlineargs This is a test
Reversed argument: sihT
Reversed argument: si
Reversed argument: a
Reversed argument: tset
Sum of integers given is: 0
% ./cmdlineargs And 123 another 12
Reversed argument: dnA
Reversed argument: rehtona
Sum of integers given is: 135
% ./cmdlineargs minus five -5 plus twenty two 22
Reversed argument: sunim
Reversed argument: evif
Reversed argument: sulp
Reversed argument: ytnewt
Reversed argument: owt
Sum of integers given is: 17
%
/* File: addnumbs.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *addnumbs(char *, char *);
int main(int argc, char *argv[])
{ char *sum;
if (argc != 3) { /* Run with exactly two arguments */
printf("Usage: %s <numb1> <numb2>\n", argv[0]); return 1; }
if ((sum = addnumbs(argv[1], argv[2])) == NULL) { /* Compute sum */
printf("Sorry, a memory problem occurred\n"); return 1; }
printf("%s + %s = %s\n", argv[1], argv[2], sum);
free(sum); /* Free memory malloc'ed by addnumbs */
return 0;
}
char *addnumbs(char *s1, char *s2)
{ int i, j, k, d1, d2, sum, carry;
char *s3, *tmp, *result;
i = strlen(s1)-1; /* Index to last character of first number */
j = strlen(s2)-1; /* Index to last character of second number */
k = (i > j) ? (i+1) : (j+1); /* Index to last character of sum */
if ((s3 = malloc((k+2)*sizeof(char))) == NULL)
return NULL; /* Allocate memory for result */
s3[k+1] = '\0'; /* Proper termination of resulting string */
carry = 0; /* Initial carry for addition */
for ( ; k >= 0 ; i--, j--, k--) {
/* Loop till first digit of result is computed */
d1 = (i >= 0) ? (s1[i]-'0') : 0; /* Get i-th digit of s1 */
d2 = (j >= 0) ? (s2[j]-'0') : 0; /* Get j-th digit of s2 */
sum = d1+d2+carry; /* Sum two digits */
carry = sum/10; /* Next carry */
s3[k] = sum%10+'0'; /* Put k-th digit of result */
}
tmp = s3; /* Save s3 for freeing it afterwards */
while (*s3 == '0') /* Eat leading 0s in the result */
s3++;
if(*s3 == '\0') /* At least one digit is needed */
s3--;
if ((result = malloc((strlen(s3)+1)*sizeof(char))) == NULL)
return NULL; /* Allocate memory for result without leading 0s */
strcpy(result, s3); /* Copy corrected s3 to result */
free(tmp); /* Free original s3 */
return result;
}
% gcc -o addnumbs addnumbs.c
% ./addnumbs 12345 67890
12345 + 67890 = 80235
% ./addnumbs 125 88275346771923625166
125 + 88275346771923625166 = 88275346771923625291
% ./addnumbs 999999999999999 999999
999999999999999 + 999999 = 1000000000999998
% ./addnumbs 0000000000023 00000057
0000000000023 + 00000057 = 80
% ./addnumbs 3252352362364362362366 3262363262362363622
3252352362364362362366 + 3262363262362363622 = 3255614725626724725988
% ./addnumbs 0000000 0000000000
0000000 + 0000000000 = 0
%
Στον τυπικό ορισμό της malloc, ο τύπος του size είναι size_t, αλλά αυτό πρακτικά είναι unsigned int. ↩
Επισήμως ο τύπος επιστροφής της συνάρτησης είναι size_t, αλλά αυτός, πρακτικά, είναι unsigned int. ↩
Πώς θα υλοποιούσαμε τις strlen και strcat; ↩
15 ↩
Μόνο, προσοχή και εδώ, το i πρέπει να κυμαίνεται, στο παράδειγμά μας, από 0 έως 9 και το j από 0 έως 7. ↩
Εδώ το funvar είναι το όνομα συγκεκριμένης συνάρτησης, όχι δείκτης σε συνάρτηση, που επιστρέφει δείκτη σε ακέραιο (int *) και έχει μία τυπική παράμετρο τύπου char *. ↩