بل

بلوک در C

{one: "۱ تمرین", other: "%{count} تمرین"}

درباره‌ی بلوک

«بلوک» مجموعه‌ای از دستورها و اعلان‌هاست که در قالب یک واحد در کنار هم گروه‌بندی می‌شوند. بلوک‌ها اغلب، اما نه همیشه، درون آکولادها تعریف می‌شوند. بلوک‌ها می‌توانند درون توابع و درون بلوک‌های دیگر تودرتو شوند.

در مثال زیر، 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'
ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود