Οδηγός Μελέτης - Εισαγωγή στον Προγραμματισμό

Διάλεξη 14 · 21/11/2025 · Διαφάνειες (PDF) · Σημειώσεις 4 · Σημειώσεις 5 · Σημειώσεις 6 · Εργαστήριο 8

Κεφάλαιο 14: Εμβέλεια, Μνήμη και Συμβολοσειρές

Στόχοι: μετά από αυτό το κεφάλαιο θα μπορείτε να βρίσκετε την εμβέλεια κάθε μεταβλητής, να ξεχωρίζετε τοπικές, παγκόσμιες και στατικές μεταβλητές και να λέτε σε ποια μνήμη ζει η καθεμία· να εξηγείτε την επισκίαση· και να χρησιμοποιείτε (και να υλοποιείτε) τις strlen, strcmp, strcpy και strcat, γνωρίζοντας τους κινδύνους τους.

Προαπαιτούμενα: Κεφάλαιο 10, Κεφάλαιο 12, Κεφάλαιο 13

Χρόνος μελέτης: ~2 ώρες

Σύνοψη

Η διάλεξη κλείνει την εικόνα της μνήμης ενός προγράμματος C. Ξεκινά από την εμβέλεια: από πού είναι ορατή μια μεταβλητή, ανάλογα με το πού δηλώθηκε, και τι γίνεται όταν δύο μεταβλητές έχουν το ίδιο όνομα. Έπειτα προσθέτει την τρίτη κατηγορία μνήμης, την παγκόσμια / στατική, όπου ζουν οι παγκόσμιες και οι static μεταβλητές και τα string literals, και δείχνει με το nm πώς τα καταγράφει ο μεταγλωττιστής. Τέλος, περνά στις συμβολοσειρές και στις βασικές συναρτήσεις της string.h, με πιθανές υλοποιήσεις τους. Είναι καθημερινά εργαλεία για κάθε πρόγραμμα που δουλεύει με κείμενο ή με ορίσματα γραμμής εντολών.

Θεωρία

§14.1 Εμβέλεια μεταβλητής

Εμβέλεια (scope) μιας μεταβλητής λέγεται το μέρος του προγράμματος όπου η μεταβλητή είναι ορατή / προσβάσιμη με το όνομά της. Την εμβέλεια την καθορίζει το σημείο της δήλωσης. Υπάρχουν δύο είδη μεταβλητών και εμβέλειας: οι τοπικές μεταβλητές (local variables), που δηλώνονται μέσα σε συνάρτηση, και οι παγκόσμιες μεταβλητές (global variables), που δηλώνονται έξω από κάθε συνάρτηση.

§14.2 Τοπικές μεταβλητές

Μια τοπική μεταβλητή δηλώνεται μέσα στο σώμα μιας συνάρτησης. Η εμβέλειά της αρχίζει από το σημείο της δήλωσης και φτάνει ως το τέλος του block εντολών ({ … }) όπου ορίστηκε: πριν από τη δήλωση το όνομα δεν υπάρχει ακόμη, μετά το } δεν υπάρχει πια. Οι τοπικές μεταβλητές αποθηκεύονται στη στοίβα (stack), στο frame της κλήσης (Κεφάλαιο 13). Οι παράμετροι είναι κι αυτές τοπικές μεταβλητές, με εμβέλεια όλο το σώμα της συνάρτησης· αρχικοποιούνται κατά την κλήση με τις τιμές των ορισμάτων.

Αφού μια τοπική μεταβλητή δεν είναι ορατή σε άλλη συνάρτηση, μπορούμε να χρησιμοποιούμε το ίδιο όνομα σε διαφορετικές συναρτήσεις: κάθε δήλωση είναι άλλη μεταβλητή, σε άλλη θέση μνήμης. Για τον ίδιο λόγο, το foo(i) δίνει στη foo αντίγραφο της τιμής του i· ό,τι κι αν κάνει η foo στην παράμετρό της, το i του καλούντος δεν αλλάζει.

§14.3 Παγκόσμιες μεταβλητές

Μια παγκόσμια μεταβλητή δηλώνεται έξω από οποιαδήποτε συνάρτηση. Η εμβέλειά της είναι από το σημείο της δήλωσης ως το τέλος του αρχείου: κάθε συνάρτηση που ορίζεται πιο κάτω μπορεί να τη διαβάσει και να την αλλάξει.

int baz = 42;
void foo() { printf("baz: %d\n", baz); }   /* βλέπει την baz */
void bar() { baz++; }                      /* και αυτή */

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

§14.4 Επισκίαση μεταβλητής

Όταν μια δήλωση μεταβλητής βρίσκεται μέσα στην εμβέλεια άλλης μεταβλητής με το ίδιο όνομα, η εσωτερική μεταβλητή επισκιάζει (shadows) την εξωτερική: μέσα στο εσωτερικό block το όνομα αναφέρεται στην εσωτερική. Η εξωτερική δεν αλλάζει και ξαναγίνεται ορατή όταν κλείσει το block. Το ίδιο ισχύει για εμφωλευμένα blocks μιας συνάρτησης: ισχύει πάντα η πιο «κοντινή» δήλωση.

flowchart TD
    F["Αρχείο: int baz = 42; (παγκόσμια)"] --> M["main: int baz = 43; (τοπική)"]
    M --> P["printf(baz) μέσα στη main: 43"]
    F --> O["άλλες συναρτήσεις: 42"]

Σχήμα: η τοπική baz της main επισκιάζει την παγκόσμια μόνο μέσα στη main.

Η επισκίαση είναι νόμιμη C αλλά συχνή πηγή λαθών: νομίζουμε ότι αλλάζουμε μια μεταβλητή και αλλάζουμε άλλη. Ο gcc προειδοποιεί γι’ αυτήν με -Wshadow.

§14.5 Στατικές μεταβλητές

Μια μεταβλητή λέγεται στατική (static) όταν διατηρεί την τιμή της ανάμεσα σε κλήσεις συναρτήσεων, μέχρι το τέλος του προγράμματος. Τη δηλώνουμε μέσα σε συνάρτηση με τη λέξη static, π.χ. static int counter = 0;. Η εμβέλειά της είναι όπως μιας τοπικής (μόνο η συνάρτηση τη βλέπει), ο χρόνος ζωής της όμως είναι όλο το πρόγραμμα: δεν ζει στη στοίβα, οπότε δεν χάνεται όταν επιστρέφει η συνάρτηση. Η αρχικοποίηση στη δήλωση γίνεται μόνο μία φορά, πριν από την πρώτη κλήση· κάθε επόμενη κλήση βρίσκει την τιμή που άφησε η προηγούμενη.

Προσοχή: αν η ανάθεση γραφτεί έξω από τη δήλωση (static int counter; και μετά counter = 0;), είναι απλή εντολή που εκτελείται σε κάθε κλήση, και η τιμή δεν διατηρείται. Οι σημειώσεις αναφέρουν και μια δεύτερη σημασία: το static μπροστά σε παγκόσμια μεταβλητή περιορίζει την εμβέλειά της στο αρχείο όπου ορίζεται.

§14.6 Η παγκόσμια / στατική μνήμη

Ένα πρόγραμμα C έχει τρεις κατηγορίες μνήμης: τη στοίβα (stack), τον σωρό (heap) και την παγκόσμια / στατική μνήμη (global / static memory). Η τελευταία είναι ένα μέρος της μνήμης του προγράμματος, ξεχωριστό από τη στοίβα και τον σωρό, όπου αποθηκεύονται οι παγκόσμιες μεταβλητές, οι στατικές μεταβλητές και δεδομένα του προγράμματος (ακόμη και ο ίδιος ο κώδικας). Αρχικοποιείται όταν ξεκινά το πρόγραμμα και αποδεσμεύεται όταν τερματίσει. Έχει υποκατηγορίες (data, code, .bss), που η διάλεξη αφήνει για άλλο μάθημα.

Κατηγορία Τι κρατά Πότε δεσμεύεται / αποδεσμεύεται
στοίβα τοπικές μεταβλητές, παράμετροι στην κλήση / στην επιστροφή
σωρός ό,τι ζητάμε με malloc με malloc / με free
παγκόσμια / στατική παγκόσμιες, static, string literals, κώδικας στην αρχή / στο τέλος του προγράμματος

§14.7 Σύμβολα, .bss και data

Κάθε αρχείο C μπορεί να ορίζει τις δικές του παγκόσμιες μεταβλητές, και ο μεταγλωττιστής σώζει τα ονόματά τους ως σύμβολα (symbols) στο αρχείο αντικειμένου (.o). Το εργαλείο nm τα εμφανίζει με ένα γράμμα για το είδος τους: T για κώδικα (συναρτήσεις), B για το .bss (μεταβλητές χωρίς αρχικοποίηση ή με 0), D για το data (μεταβλητές αρχικοποιημένες με μη μηδενικές τιμές). Το μικρό γράμμα σημαίνει σύμβολο τοπικό στο αρχείο: η static int counter της foo εμφανίζεται ως b counter.0.

Η διαφορά .bss και data φαίνεται στο μέγεθος του αρχείου. Για το .bss αρκεί να γραφτεί πόσος χώρος χρειάζεται· ο χώρος δίνεται γεμάτος μηδενικά όταν ξεκινά το πρόγραμμα. Αν όμως η μεταβλητή αρχικοποιηθεί με μη μηδενικές τιμές, ο μεταγλωττιστής αποθηκεύει στο αρχείο όλο τον χώρο και όλες τις τιμές. Έτσι ένας τεράστιος παγκόσμιος πίνακας χωρίς αρχικοποίηση δεν μεγαλώνει το .o, ενώ με = {1} το κάνει εκατοντάδες MB.

§14.8 String literals στη στατική μνήμη

Οι συμβολοσειρές σε εισαγωγικά (string literals) που δεν χρησιμοποιούνται για την αρχικοποίηση ενός τοπικού πίνακα αποθηκεύονται κι αυτές στη στατική μνήμη:

char *str1 = "Hello World";   /* δείκτης σε string της στατικής μνήμης */
char str2[] = "Hello World";  /* τοπικός πίνακας 12 char, στη στοίβα */

Ο str1 είναι δείκτης που δείχνει σε ένα literal της στατικής μνήμης· δύο ίδια literals μπορεί να είναι το ίδιο αντίγραφο, στην ίδια διεύθυνση. Στον str2 οι χαρακτήρες αντιγράφονται σε νέο πίνακα στη στοίβα, με δική του διεύθυνση. Οι σημειώσεις προσθέτουν ότι τα literals δεν επιτρέπεται να αλλάξουν: τους χαρακτήρες του str2 μπορούμε να τους αλλάξουμε, αυτούς όπου δείχνει ο str1 όχι.

§14.9 Συμβολοσειρές

Ένας πίνακας από χαρακτήρες λέγεται αλφαριθμητικό ή συμβολοσειρά (string). Οι τρεις δηλώσεις που είδαμε στο Κεφάλαιο 10 είναι ισοδύναμες:

char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd',
                '\n', '\0'};
char hello[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 10, 0};
char hello[] = "Hello World\n";

Τα string τερματίζονται πάντα με το null byte '\0'. Η μορφή με τα εισαγωγικά το προσθέτει μόνη της, οπότε ο πίνακας έχει 13 θέσεις για 12 χαρακτήρες. Το null byte είναι ο μόνος τρόπος να ξέρει μια συνάρτηση πού τελειώνει ένα string: όλες οι συναρτήσεις παρακάτω διατρέχουν τους χαρακτήρες μέχρι να βρουν '\0'. Σε ένα string αναφερόμαστε και με δείκτη char * στον πρώτο χαρακτήρα του· έτσι περνά σε συναρτήσεις.

§14.10 Οι συναρτήσεις της string.h

Οι συναρτήσεις βιβλιοθήκης για συμβολοσειρές δηλώνονται στο string.h (#include <string.h>):

size_t strlen(const char *s);
int strcmp(const char *s1, const char *s2);
char *strcpy(char *dst, const char *src);
char *strcat(char *dst, const char *src);

Η strcpy είναι ιδιαίτερα επικίνδυνη (θέμα ασφάλειας!): γράφει στη μνήμη χωρίς να ελέγχει αν υπάρχει αρκετός χώρος. Αν ο προορισμός είναι μικρότερος, γράφει πάνω σε ό,τι ακολουθεί (buffer overflow). Τον έλεγχο πρέπει να τον κάνουμε εμείς, και ελαχιστοποιούμε τη χρήση της. Η strncpy (man strncpy) δέχεται και μέγιστο πλήθος χαρακτήρων: λίγο καλύτερη, αλλά πάλι κακή επιλογή, γιατί μπορεί να αφήσει τον προορισμό χωρίς null byte. Η strcat έχει ακριβώς τα ίδια προβλήματα: ελέγχουμε πάντα ότι ο πίνακας του 1ου ορίσματος χωρά και τα δύο string και το null byte.

Η διάλεξη αφήνει για μελέτη και τις strdup, strchr και strtok (δείτε «Διάβασμα»)· την strtok τη χρησιμοποιεί το Εργαστήριο 8.

§14.11 Η έκφραση *str++

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

Έκφραση Τιμή Τι αλλάζει
*str++, *(str++) ο τρέχων χαρακτήρας ο δείκτης str προχωρά
(*str)++ ο τρέχων χαρακτήρας ο χαρακτήρας αυξάνεται κατά 1

Παραδείγματα

§14.12 Η εμβέλεια μέσα στη foo

Εφαρμογή της «Τοπικές μεταβλητές».

void foo(int bar) {
  printf("%d\n", bar);
  int i = 42;
  printf("%d\n", i);
  int j;
  for (j = 0; j < i; j++)
    printf("%d %d\n", bar, i * j);
}

Η εμβέλεια του bar είναι όλο το σώμα (αρχικοποιείται κατά την κλήση)· του i, από το int i = 42; ως το } (στην πρώτη printf δεν υπάρχει ακόμη)· του j, από το int j; ως το }.

§14.13 Το ίδιο όνομα σε δύο συναρτήσεις

Εφαρμογή της «Τοπικές μεταβλητές». Τι επιστρέφει αυτό το πρόγραμμα;

void foo() {
  int i = 43;
}
int main() {
  int i = 42;
  foo();
  return i;
}
$ gcc -o local local.c
$ ./local
$ echo $?
42

Το i της foo είναι άλλη μεταβλητή, στο frame της foo· το 43 δεν αγγίζει το i της main. Το ίδιο ισχύει με όρισμα: στο local2.c η foo γίνεται void foo(int i) { i++; } και η main καλεί foo(i);. Το ./local2; echo $? τυπώνει πάλι 42: η παράμετρος πήρε αντίγραφο του 42 και έγινε 43, το i της main όχι.

§14.14 Μια παγκόσμια και μια τοπική baz

Εφαρμογή της «Επισκίαση μεταβλητής». Τι θα τυπώσει;

#include <stdio.h>
int baz = 42;
int main() {
  int baz = 43;
  printf("baz: %d\n", baz);
  return 0;
}
$ ./shadow
baz: 43

Η παγκόσμια baz έχει εμβέλεια όλο το αρχείο, αλλά μέσα στη main την επισκιάζει η τοπική.

§14.15 Ένας μετρητής με static

Εφαρμογή της «Στατικές μεταβλητές». Τι θα τυπώσει;

#include <stdio.h>
void foo() {
  static int counter = 0; int i = 0;
  counter++; i++;
  printf("%d %d\n", counter, i);
}
int main() {
  foo(); foo(); foo();
  return 0;
}
$ ./static
1 1
2 1
3 1

Το counter αρχικοποιείται μία φορά και μετρά τις κλήσεις· το i ξαναγίνεται 0 σε κάθε κλήση. Αν η πρώτη γραμμή της foo γίνει static int counter; int i = 0; counter = 0;, το counter = 0 εκτελείται σε κάθε κλήση και το πρόγραμμα τυπώνει 1 1 τρεις φορές.

§14.16 Παράδειγμα με κάθε μνήμη

Εφαρμογή της «Η παγκόσμια / στατική μνήμη».

#include <stdlib.h>
char global[3] = {42, 24, 55};
void foo(char c1, char c2) {
  char c3 = 63;
  char * tmp = malloc(3 * sizeof(char));
  tmp[0] = 42; tmp[1] = 255; tmp[2] = 255;
}
int main() {
  foo(61, 62);
  return 0;
}

Κατά την εκτέλεση της foo (οι διευθύνσεις είναι ενδεικτικές):

Περιοχή Περιεχόμενο
παγκόσμια / στατική global: 42, 24, 55 (bytes 1–3)
σωρός το μπλοκ της malloc: 42, 255, 255
στοίβα c1 = 61 (byte 32000), c2 = 62 (31999), c3 = 63 (31998)

Ο global υπάρχει σε όλη την εκτέλεση. Τα c1, c2, c3 (και ο ίδιος ο δείκτης tmp) ζουν στη στοίβα μόνο όσο τρέχει η foo. Το μπλοκ του σωρού μένει δεσμευμένο και μετά την επιστροφή, αφού δεν καλείται free, και όταν χαθεί ο tmp δεν μπορούμε πια να το ελευθερώσουμε.

§14.17 Ο πίνακας των 400 MB

Εφαρμογή της «Σύμβολα, .bss και data». Το memory.c:

int global_buffer[10];
void foo() {
  static int counter = 0;
  counter++;
}
int main() {
  foo();
  return 0;
}
$ gcc -c memory.c
$ nm memory.o
0000000000000028 b counter.0
0000000000000000 T foo
0000000000000000 B global_buffer
0000000000000016 T main
$ du -sh memory.o
4.0K    memory.o

Οι foo και main είναι κώδικας (T), ο global_buffer και ο counter είναι στο .bss. Με int global_buffer[100000000]; (400.000.000 bytes) το nm δείχνει τον counter στο 0000000017d78400, δηλαδή 400.000.000, αμέσως μετά τον πίνακα, αλλά το memory.o μένει 4.0K: γράφεται μόνο το μέγεθος. Με int global_buffer[100000000] = {1}; όμως:

$ gcc -c memory.c
$ nm memory.o
0000000000000000 b counter.0
0000000000000000 T foo
0000000000000000 D global_buffer
0000000000000016 T main
$ du -sh memory.o
382M    memory.o

Ο πίνακας πέρασε στο data (D) και ο μεταγλωττιστής έγραψε στο αρχείο όλα τα 400 MB (382 MiB). Οι Συχνές Ερωτήσεις του μαθήματος συνδέουν το σφάλμα section size is larger than file size με τέτοιους πίνακες: αν τον θέλετε με μηδενικά, μην τον αρχικοποιείτε, απλώς δηλώστε τον παγκόσμιο.

§14.18 Δείκτες και πίνακες με το ίδιο literal

Εφαρμογή της «String literals στη στατική μνήμη».

#include <stdio.h>
int main() {
  char * str1 = "Hello World", * str2 = "Hello World";
  printf("%p %p\n", str1, str2);
  return 0;
}
$ gcc -o const const.c
$ ./const
0x5588197ac004 0x5588197ac004

Οι δύο δείκτες δείχνουν στο ίδιο αντίγραφο του literal. Με πίνακες, char str1[] = "Hello World", str2[] = "Hello World";, η έξοδος γίνεται:

$ ./const
0x7ffc2576ccc4 0x7ffc2576ccb8

Δύο διευθύνσεις στη στοίβα (0x7ffc…), σε απόσταση 12 bytes: όσο ένας πίνακας των 11 χαρακτήρων και του null byte.

§14.19 Το μήκος του πρώτου ορίσματος

Εφαρμογή της «Οι συναρτήσεις της string.h».

#include <stdio.h>
#include <string.h>
int main(int argc, char ** argv) {
  if (argc != 2) return 1;
  printf("Arg1 length: %zu\n", strlen(argv[1]));
  return 0;
}
$ gcc -o strlen strlen.c
$ ./strlen "hello world"
Arg1 length: 11

Τα εισαγωγικά κάνουν το hello world ένα όρισμα· το κενό μετράει, το null byte όχι.

§14.20 Η strcmp από τη γραμμή εντολών

Εφαρμογή της «Οι συναρτήσεις της string.h».

#include <stdio.h>
#include <string.h>
int main(int argc, char ** argv) {
  if (argc != 3) return 1;
  printf("strcmp(arg1, arg2): %d\n",
         strcmp(argv[1], argv[2]));
  return 0;
}
$ ./strcmp foo bar
strcmp(arg1, arg2): 4
$ ./strcmp foo foo
strcmp(arg1, arg2): 0
$ ./strcmp f bar
strcmp(arg1, arg2): 4
$ ./strcmp bar f
strcmp(arg1, arg2): -4
$ ./strcmp bar c
strcmp(arg1, arg2): -1
$ ./strcmp bar ba
strcmp(arg1, arg2): 114

Στο foo/bar η πρώτη διαφορά είναι 'f' - 'b' = 102 − 98 = 4. Στο bar/ba το ba τελειώνει πρώτο, οπότε συγκρίνεται το 'r' (114) με το '\0' (0). Η σειρά είναι λεξικογραφική: το f είναι «μεγαλύτερο» από το bar παρότι είναι πιο κοντό.

§14.21 Αντιγραφή και συνένωση

Εφαρμογή της «Οι συναρτήσεις της string.h».

#include <stdio.h>
#include <string.h>
int main(int argc, char ** argv) {
  char hello[16] = "Hello!";
  char world[16];
  strcpy(world, hello);
  printf("world: %s\n", world);
  return 0;
}
$ ./strcpy
world: Hello!

Το πρόγραμμα της strcat έχει στο σώμα του:

char hello[16] = "Hello ";
char world[16] = "World!";
strcat(hello, world);
printf("concat: %s\n", hello);
$ ./strcat
concat: Hello World!

Ο χώρος φτάνει: το "Hello World!" θέλει 12 χαρακτήρες και το null byte, 13 από τα 16 bytes του hello. Με char hello[8] η strcat θα έγραφε έξω από τον πίνακα.

§14.22 Πιθανές υλοποιήσεις

Εφαρμογή της «Η έκφραση *str++». Οι υλοποιήσεις των διαφανειών (η βιβλιοθήκη δηλώνει τα ορίσματα που δεν αλλάζουν ως const char *):

size_t strlen(char * str) {
  size_t length = 0;
  while (*str++) length++;
  return length;
}

int strcmp(char * str1, char * str2) {
  while (*str1 && (*str1 == *str2)) {
    str1++;
    str2++;
  }
  return *str1 - *str2;
}

char * strcpy(char * dst, char * src) {
  char * original_dst = dst;
  while (*src) *dst++ = *src++;
  *dst = '\0';
  return original_dst;
}

char *strcat(char *dst, char *src) {
  char *original_dst = dst;
  while (*dst) dst++;
  while (*src) *dst++ = *src++;
  *dst = '\0';
  return original_dst;
}

§14.23 Είναι παλινδρομικό;

Εφαρμογή της «Οι συναρτήσεις της string.h». Ένα string είναι παλινδρομικό αν διαβάζεται ίδιο και από τις δύο μεριές. Η λύση των διαφανειών:

int isPalindrome(char *str) {
    int left = 0;
    int right = strlen(str) - 1;
    while (left < right) {
        if (str[left] != str[right]) return 0;
        left++;
        right--;
    }
    return 1;
}

Δύο δείκτες θέσης ξεκινούν από τις άκρες και πλησιάζουν· με την πρώτη διαφορά η απάντηση είναι «όχι», κι αν συναντηθούν, «ναι». Το strlen(str) - 1 είναι ο τελευταίος ορατός χαρακτήρας, πριν από το null byte. Η διάλεξη θέτει και την ερώτηση «πώς ελέγχω αν το πρώτο όρισμα του προγράμματός μου είναι "--boo";», που τη βρίσκετε στις «Ασκήσεις».

Κύρια σημεία

  1. Εμβέλεια είναι το μέρος του προγράμματος όπου μια μεταβλητή είναι ορατή, και την καθορίζει το σημείο της δήλωσης.
  2. Μια τοπική μεταβλητή (και μια παράμετρος) είναι ορατή από τη δήλωσή της ως το τέλος του block της και ζει στη στοίβα· με το ίδιο όνομα σε άλλη συνάρτηση είναι άλλη μεταβλητή.
  3. Μια παγκόσμια μεταβλητή δηλώνεται έξω από συναρτήσεις και είναι ορατή από τη δήλωσή της ως το τέλος του αρχείου.
  4. Μια εσωτερική δήλωση με το ίδιο όνομα επισκιάζει την εξωτερική μέσα στο block της.
  5. Μια static μεταβλητή συνάρτησης αρχικοποιείται μία φορά και κρατά την τιμή της ανάμεσα στις κλήσεις, εκτός αν της αναθέσουμε τιμή σε ξεχωριστή εντολή.
  6. Υπάρχουν τρεις κατηγορίες μνήμης, η στοίβα, ο σωρός και η παγκόσμια / στατική μνήμη, που ζει από την αρχή ως το τέλος του προγράμματος.
  7. Οι παγκόσμιες και οι στατικές μεταβλητές γίνονται σύμβολα του αρχείου αντικειμένου (nm), και μόνο όσες έχουν μη μηδενικές αρχικές τιμές πιάνουν χώρο στο αρχείο.
  8. Τα string literals που δεν αρχικοποιούν τοπικό πίνακα ζουν στη στατική μνήμη, ενώ ο char s[] = "…" είναι δικό του αντίγραφο στη στοίβα.
  9. Ένα string είναι πίνακας χαρακτήρων που τελειώνει πάντα με το null byte '\0'.
  10. Η strlen μετρά χωρίς το null byte, η strcmp επιστρέφει 0, θετικό ή αρνητικό, η strcpy αντιγράφει και η strcat συνενώνει.
  11. Το *str++ σημαίνει *(str++), όχι (*str)++: η προτεραιότητα των τελεστών μετράει.
  12. Οι strcpy και strcat δεν ελέγχουν αν χωρά το αποτέλεσμα, οπότε τον έλεγχο τον κάνουμε πάντα εμείς.

Ορολογία

Ελληνικά English Σύντομος ορισμός
εμβέλεια scope Το μέρος του προγράμματος όπου ένα όνομα είναι ορατό.
τοπική μεταβλητή local variable Δηλώνεται σε συνάρτηση· ορατή ως το τέλος του block.
παγκόσμια μεταβλητή global variable Δηλώνεται έξω από συναρτήσεις· ορατή ως το τέλος του αρχείου.
επισκίαση shadowing Εσωτερική δήλωση με ίδιο όνομα κρύβει την εξωτερική.
στατική μεταβλητή static variable Κρατά την τιμή της ανάμεσα σε κλήσεις, ως το τέλος του προγράμματος.
χρόνος ζωής lifetime Το διάστημα της εκτέλεσης όπου υπάρχει η μεταβλητή.
παγκόσμια / στατική μνήμη global / static memory Μνήμη που ζει όσο το πρόγραμμα.
σύμβολο symbol Όνομα συνάρτησης ή μεταβλητής σε αρχείο αντικειμένου.
σταθερή συμβολοσειρά string literal Κείμενο σε εισαγωγικά μέσα στον κώδικα.
συμβολοσειρά / αλφαριθμητικό string Πίνακας char που τελειώνει με '\0'.
null byte null byte Ο χαρακτήρας '\0' που σημαδεύει το τέλος.
συνένωση concatenation Προσάρτηση ενός string στο τέλος άλλου.
υπερχείλιση buffer buffer overflow Εγγραφή πέρα από το τέλος ενός πίνακα.

Διάβασμα

Συχνά λάθη

Τι δυσκόλεψε την τάξη

Από τα Kahoot των διαλέξεων: οι ερωτήσεις όπου μια λάθος απάντηση μάζεψε πολλές ψήφους, με το ποσοστό σωστών απαντήσεων.

Ερωτήσεις κατανόησης

Kahoot από το αμφιθέατρο (Κ14.1–Κ14.8)

Ερωτήσεις που παίχτηκαν στις διαλέξεις, με το ποσοστό των φοιτητών που απάντησαν σωστά.

Ασκήσεις

Ζέσταμα: από τις διαφάνειες (Α14.1–Α14.8)

Εργαστήριο (Α14.9)

Εργασίες (Α14.10)

Θέματα εξετάσεων (Α14.11–Α14.22)

Σχετικές ασκήσεις από άλλα κεφάλαια

  1. Η παράμετρος: όλο το σώμα της συνάρτησης. Η παγκόσμια: από τη δήλωσή της ως το τέλος του αρχείου. [^q2]: Στην παγκόσμια / στατική μνήμη, που υπάρχει από την αρχή ως το τέλος του προγράμματος, όχι στο frame της στοίβας. [^q3]: Ο πρώτος είναι στο .bss: γράφεται μόνο το μέγεθος και ο χώρος γεμίζει με μηδενικά στην εκκίνηση. Ο δεύτερος είναι στο data: όλες οι τιμές γράφονται στο αρχείο. [^q4]: 3 και 4: η strlen δεν μετρά το '\0', ο πίνακας όμως το περιέχει. [^q5]: Το *p++ δίνει την τιμή όπου δείχνει ο p και προχωρά τον δείκτη· το (*p)++ αυξάνει την τιμή και ο δείκτης μένει. [^q6]: strcmp(a, b) == 0· το a == b συγκρίνει διευθύνσεις. ↩

Κατεβάστε το κεφάλαιο: PDF · Markdown · GitHub