Ένα μπλοκ είναι ένα σύνολο από εντολές και δηλώσεις που ομαδοποιούνται σε μία συντακτική μονάδα. Τα μπλοκ ορίζονται συχνά, αλλά όχι πάντα, μέσα σε άγκιστρα. Τα μπλοκ μπορούν να εμφωλευτούν μέσα σε συναρτήσεις και μέσα σε άλλα μπλοκ.
Στο παρακάτω παράδειγμα, το number ορίζεται μέσα στη συνάρτηση main.
Το flag ορίζεται μέσα στο μπλοκ if, το οποίο είναι εμφωλευμένο μέσα στη συνάρτηση main.
Οι μεταβλητές που ορίζονται μέσα σε ένα μπλοκ είναι έγκυρες μόνο μέσα σε αυτό το μπλοκ.
Έτσι, η προσπάθεια μεταγλώττισης του κώδικα οδηγεί σε σφάλμα μεταγλώττισης.
#include <stdio.h>
int main() {
int number = 1;
if (number == 1) {
int flag = 1;
printf("%d", flag);
}
// this line generates a compilation error
// error: 'flag' undeclared (first use in this function)
printf("%d", flag);
}
Το μήνυμα σφάλματος μπορεί να είναι μπερδεμένο, καθώς το flag χρησιμοποιείται νωρίτερα στη συνάρτηση.
Ωστόσο, το flag ορίζεται μέσα σε ένα μπλοκ μέσα στη συνάρτηση.
Το εσωτερικό μπλοκ μπορεί να "δει" τη μεταβλητή number στην εξωτερική συνάρτηση, αλλά η εξωτερική συνάρτηση δεν μπορεί να "δει" τη μεταβλητή flag που ορίζεται μέσα στο εσωτερικό της μπλοκ.
Μόλις η εκτέλεση φύγει από το μπλοκ, η μεταβλητή flag καταστρέφεται αυτόματα και δεν είναι πλέον διαθέσιμη στο πρόγραμμα.
Η ίδια η συνάρτηση main βλέπει το αναγνωριστικό flag για πρώτη φορά στην τελευταία εντολή printf.
Αν αφαιρεθεί η τελευταία εντολή printf, το πρόγραμμα θα μεταγλωττιστεί και θα εκτυπώσει 1 όταν εκτελεστεί.
Αυτό συμβαίνει επειδή η μεταβλητή flag είναι έγκυρη στο μπλοκ που περιέχει την πρώτη εντολή printf.
Από τη C99 και μετά, οι εντολές if, for, while και do μίας γραμμής θεωρούνται επίσης μπλοκ.
Στο παρακάτω παράδειγμα, αν και το i δεν ορίζεται μέσα σε άγκιστρα, υπάρχει μόνο στο μπλοκ που ορίζει η εντολή for και δεν είναι ορατό στην εξωτερική συνάρτηση main.
#include <stdio.h>
int main() {
for (int i = 0, j = 11; i < j; i+=2)
printf("%d", i);
// this line generates a compilation error
// error: 'i' undeclared (first use in this function)
printf("%d", i);
}
Αν αφαιρεθεί η τελευταία εντολή printf, το πρόγραμμα θα μεταγλωττιστεί και θα εκτυπώσει 0 2 4 6 8 10 όταν εκτελεστεί.
Κάποιες γλώσσες, όπως η Go και η Rust, ορίζουν ρητά ότι οι εντολές συνθήκης και οι βρόχοι μίας γραμμής αποτελούν μπλοκ.
Απαιτούν αυτές οι εντολές μίας γραμμής να περικλείονται ακόμα σε άγκιστρα.
Μια συνάρτηση συμπεριφέρεται κάπως σαν μπλοκ. Ένα αναγνωριστικό για μια μεταβλητή που ορίζεται σε μια συνάρτηση δεν μπορεί να φανεί έξω από τη συνάρτηση, όπως ακριβώς ένα αναγνωριστικό για μια μεταβλητή που ορίζεται σε ένα μπλοκ δεν μπορεί να φανεί έξω από το μπλοκ. Δύο μεταβλητές σε δύο συναρτήσεις μπορούν να έχουν το ίδιο όνομα, αφού οι δύο συναρτήσεις δεν βλέπουν η μία μέσα στην άλλη, όπως ακριβώς δύο μεταβλητές σε δύο μπλοκ μπορούν να έχουν το ίδιο όνομα, αφού τα δύο μπλοκ δεν βλέπουν το ένα μέσα στο άλλο. Όμως οι συναρτήσεις και τα μπλοκ δεν είναι ταυτόσημα. Μια συνάρτηση δεν μπορεί να οριστεί μέσα σε άλλη συνάρτηση, αλλά ένα μπλοκ μπορεί να εμφωλευτεί μέσα σε άλλο μπλοκ. Και κάποια αναγνωριστικά μέσα σε ένα μπλοκ μπορούν να φανούν έξω από το μπλοκ. Στο παρακάτω παράδειγμα, ο κώδικας δεν θα μεταγλωττιστεί
#include <stdio.h>
int main() {
{
int i = 0;
begin:
printf("i == %d\n", i);
if (i == 0) {
i = 1;
goto begin;
}
}
{
int i = 1;
begin:
printf("i == %d\n", i);
if (i == 1) {
i = 0;
goto begin;
}
}
}
Ο κώδικας παραπάνω αποτυγχάνει να μεταγλωττιστεί με το εξής σφάλμα
/tmp/gH2TVYnTSJ.c: In function 'main':
/tmp/gH2TVYnTSJ.c:17:5: error: duplicate label 'begin'
17 | begin:
| ^~~~~
/tmp/gH2TVYnTSJ.c:7:5: note: previous definition of 'begin' was here
7 | begin:
| ^~~~~
Αν και το i μπορεί να οριστεί σε κάθε μπλοκ, τα ονόματα των label πρέπει να είναι μοναδικά μέσα σε μια συνάρτηση.
Αυτό συμβαίνει επειδή τα labels έχουν εμβέλεια συνάρτησης, δηλαδή κάθε ετικέτα είναι ορατή από τη συνάρτηση ανεξάρτητα από το πού μέσα στη συνάρτηση ορίζεται.
Έτσι, σε αντίθεση με τις μεταβλητές, ανεξάρτητα από το πού ορίζεται ένα label μέσα σε μια συνάρτηση, πρέπει να έχει μοναδικό όνομα μέσα στην ίδια συνάρτηση.
Για να δουλέψει ο κώδικας, χρειάζεται να αλλάξεις το όνομα ενός από τα labels, ώστε να είναι και τα δύο μοναδικά, όπως στο παρακάτω παράδειγμα
#include <stdio.h>
int main() {
{
int i = 0;
begin:
// prints
// i == 0
// i == 1
printf("i == %d\n", i);
if (i == 0) {
i = 1;
goto begin;
}
}
{
int i = 1;
start:
// prints
// i == 1
// i == 0
printf("i == %d\n", i);
if (i == 1) {
i = 0;
goto start;
}
}
}
Οι εντολές μίας γραμμής μπορούν να "στοιβαχτούν" και να βρίσκονται ακόμα μέσα σε ένα μπλοκ χωρίς τη χρήση αγκίστρων.
Στο παρακάτω παράδειγμα, η εντολή for περιέχει μια εντολή if, η οποία με τη σειρά της περιέχει μια εντολή printf.
Αν και υπάρχουν δύο συνολικά εντολές στο μπλοκ for, μόνο η εντολή if μίας γραμμής αξιολογείται ως το σώμα του βρόχου for.
Η εντολή printf μίας γραμμής της εντολής if με τη σειρά της αξιολογείται ως το σώμα της.
Αν τα εμφωλευμένα μπλοκ είναι το καθένα μόνο μία γραμμή, κανένα από τα μπλοκ δεν χρειάζεται άγκιστρα.
Σημείωσε ότι το σχόλιο δεν χρειάζεται άγκιστρα για να περικλείσει αυτό και την εντολή printf μέσα στο μπλοκ if, καθώς το σχόλιο δεν είναι εντολή.
Η στοίχιση κάνει σαφές τι συμβαίνει, αλλά μια ομάδα ανάπτυξης μπορεί να έχει τις δικές της οδηγίες ύφους κώδικα για την τοποθέτηση αγκίστρων σε τέτοιες περιπτώσεις, ώστε να αφαιρεθεί κάθε ασάφεια.
#include <stdio.h>
int main() {
for (int i = 0, j = 11; i < j; i++)
if (i & 1 == 1)
// prints 1 3 5 7 9
printf("%d ", i);
}
Είδαμε ότι οι τιμές που ορίζονται μέσα σε ένα μπλοκ δεν είναι προσβάσιμες από έξω από το μπλοκ.
Και είδαμε ότι οι τιμές που ορίζονται έξω από ένα μπλοκ είναι συνήθως προσβάσιμες από μέσα από το μπλοκ.
Είναι δυνατόν μια μεταβλητή έξω από ένα μπλοκ να κρυφτεί μέσα σε ένα μπλοκ, αν το αναγνωριστικό αυτής της μεταβλητής χρησιμοποιείται για τον ορισμό μιας μεταβλητής του μπλοκ.
Στο παρακάτω παράδειγμα, το αναγνωριστικό number χρησιμοποιείται τόσο για μια μεταβλητή επιπέδου συνάρτησης όσο και για μια μεταβλητή επιπέδου μπλοκ.
Λέμε ότι η μεταβλητή επιπέδου μπλοκ κρύβει ή επισκιάζει τη μεταβλητή επιπέδου συνάρτησης.
#include <stdio.h>
int main() {
int number = 1;
// prints number in function is 1
printf("number in function is %d\n", number);
if (number == 1) {
// the definition of a block-level variable using the same name
// shadows the function-level variable
int number = 2;
// prints number in function is 2
printf("number in if block is %d\n", number);
}
// prints number in function is 1
printf("number in function is %d\n", number);
}
Οι μεταγλωττιστές μπορεί να παράγουν σφάλμα αν γίνει προσπάθεια να επισκιαστεί μια παράμετρος συνάρτησης. Το παρακάτω παράδειγμα
#include <stdio.h>
void print_number(int number) {
// will fail to compile
int number = 2;
printf("number in function is %d\n", number);
}
int main() {
int number = 1;
printf("number in main is %d\n", number);
print_number(number);
printf("number in main is %d\n", number);
}
οδηγεί στα εξής σφάλματα σε δύο πλατφόρμες
/tmp/jIvhD5UOop.c: In function 'print_number':
/tmp/jIvhD5UOop.c:5:9: error: 'number' redeclared as different kind of symbol
5 | int number = 2;
| ^~~~~~
/tmp/jIvhD5UOop.c:3:23: note: previous definition of 'number' was here
3 | void print_number(int number) {
| ~~~~^~~~~~
και
redefinition of formal parameter 'number'