Menu

C'de Kapsam: Blok, Fonksiyon ve Dosya Kapsamı

Bir C değişkeninin nerede görünür olduğu ve ne kadar yaşadığı - blok kapsamı, fonksiyon parametreleri, dosya kapsamındaki küreseller, çağrılar arasında yaşayan static yerel değişkenler, static fonksiyonlar, gölgeleme ve küresellerin neden sorun çıkardığı.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Her Değişken Hakkında İki Soru

C'deki her değişkenin iki ayrı özelliği vardır ve onları karıştırmak bu sayfadaki sürprizlerin çoğunun kaynağıdır:

  • Kapsam - ismin nerede kullanılabileceği. Bir derleme zamanı sorusu.
  • Ömür - depolamanın ne kadar var olduğu. Bir çalışma zamanı sorusu.

Genellikle birlikte hareket ederler (sıradan bir yerel bloğunda görünürdür ve tam olarak o kadar yaşar), ama static onları ayırır; burada anlaşılmaya değen anahtar kelime olmasının sebebi budur.

Blok Kapsamı

Süslü parantezler içinde bildirilen bir değişken, bildiriminden kapanış parantezine kadar görünürdür, başka hiçbir yerde değil.

İçteki blok outer'ı görebilir, çünkü onun içine yerleştirilmiştir. Dıştaki blok inner'ı göremez. Bu her süslü parantez çifti için geçerlidir - fonksiyon gövdeleri, if gövdeleri, döngü gövdeleri ya da yukarıdaki gibi çıplak bir blok.

Döngüler bunu somutlaştırır. Bir for başlığında bildirilen bir sayaç döngüye aittir:

for (int i = 0; i < 5; i++) {
    /* i burada yaşar */
}
/* i burada var olmaz */

Ki bu tam olarak istediğiniz şeydir: aynı fonksiyondaki iki döngünün her biri i'yi hiçbir karışma olmadan kullanabilir ve sayaç döngüden sonra kazara okunamaz. Değere sonradan ihtiyacınız varsa - bir aramanın durduğu indeks - onu döngüden önce bildirin.

Fonksiyon parametrelerinin kapsamı fonksiyon gövdesidir, dolayısıyla gövdenin tepesinde bildirilmiş yereller gibi davranırlar.

C99, bir bildirime yalnızca tepede değil bir blokta her yerde izin verir. Değişkenleri ilk ihtiyaç duyduğunuz yerde bildirin; kapsamı kısa olan bir değişken, yanlış olma yolu daha az olan bir değişkendir.

Ömür: Otomatik Depolama

Sıradan bir yerelin otomatik depolaması vardır: denetim bloğuna girdiğinde var olur ve denetim ayrıldığında yok edilir. Belleği yığında yaşar.

Üç kez 1 yazdırır. Her çağrı 0 ile başlatılan yepyeni bir count alır ve dönüşte atar. Özyinelemeli bir fonksiyonun her çerçevesinin her yerelin kendi kopyasına sahip olmasının sebebi de budur.

Açıkça belirtmeye değer iki sonuç. İlk değer verilmemiş bir yerel sıfır değil çöp içerir - yığında ne bayt varsa. Ve yerel bir değişkene gösterici döndürmek tanımsız davranıştır, çünkü fonksiyon döndüğü anda depolama gitmiştir:

/* HATA: return sonrası buf var olmaz */
char *broken(void) {
    char buf[64] = "hello";
    return buf;
}

static Yereller: Çağrılar Arasında Bellek

Bir yerele static koyun, ömrü tüm program olur, kapsamı tam olarak aynı kalır.

Artık sayaç 1, 2, 3 yazdırır. = 0, her çağrıda değil, main başlamadan önce bir kez çalışır.

static yereller için üç kural:

  • İlk değer bir sabit ifade olmalıdır, çünkü çalışma zamanında değil program başlangıcında uygulanır.
  • İlk değersiz bir static sıfırla başlatılır (çöp tutan otomatik bir yerelin aksine).
  • Değişken hâlâ fonksiyona özeldir. Başka hiçbir kod onu adlandıramaz.

O son nokta tüm cazibedir: bir fonksiyon, her şeyin değiştirebileceği bir küresel açığa çıkarmadan çağrılar arasında bir şey hatırlayabilir. Onu çağrı sayaçları, ilk kullanımda kurulan önbellekli arama tabloları ve tek seferlik başlatma bayrakları için kullanın.

Bedeli, fonksiyonun artık saf bir girdi-çıktı eşlemesi olmamasıdır - aynı argümanlar farklı cevaplar verebilir - ki bu test etmeyi zorlaştırır ve korumasız olarak birkaç iş parçacığından aynı anda çağırmayı güvensiz kılar. static yerelleri kolaylık olsun diye değil, bilinçli kullanın.

Dosya Kapsamı: Küreseller

Her fonksiyonun dışında bildirilen bir değişkenin dosya kapsamı vardır. Bildiriminden dosyanın sonuna kadar, altındaki her fonksiyonda görünürdür ve ömrü tüm programdır.

Küreseller varsayılan olarak sıfırla başlatılır, dolayısıyla totalOperations, = 0 olmadan bile 0'dan başlar.

Varsayılan olarak bir küreselin ayrıca dış bağlantısı vardır: aynı programdaki diğer .c dosyaları onu extern bildirerek ona ulaşabilir:

/* stats.c içinde */
int totalOperations = 0;          /* tanım - programda tam olarak bir tane */

/* main.c içinde */
extern int totalOperations;       /* bir bildirim: "bir yerde var" */

extern bildirimini bir başlık dosyasına koyun, böylece her kullanıcı aynısını görür. Asimetriye dikkat edin: extern bildirimi değişkenin var olduğunu söyler ve tam olarak bir .c dosyasının onu gerçekten tanımlaması gerekir.

Küreseller Neden Canınızı Yakar

Küreseller veri paylaşmanın en kolay yolu ve yerelleştiremeyeceğiniz hatalar yaratmanın en kolay yoludur. Dört somut sorun:

Her şey onları değiştirebilir. totalOperations yanlış bir değer tuttuğunda suçlu, herhangi bir dosyadaki herhangi bir satırdır. Bir parametreyle şüpheliler görebildiğiniz çağrı yerleridir.

Fonksiyonlar test edilemez hâle gelir. Bir küreseli okuyan bir fonksiyon tek başına çağrılamaz; önce küresel durumu kurmanız ve sonra sıfırlamayı hatırlamanız gerekir.

İsim program geneline aittir. count ya da buffer adlı bir küresel eninde sonunda başkasınınkiyle çakışacaktır.

İlk değerleri derleme zamanı sabiti olmalıdır. C'de bir küresele yalnızca sabit bir ifadeyle ilk değer verilebilir - int limit = readConfig(); derlenmez, dosya kapsamındaki int b = a * 2; de derlenmez. Hesaplanan her şey, çalışma zamanında bir hazırlık fonksiyonu tarafından atanmalıdır ve o çağrı çalışmadan önce küresele dokunan her dosya sessizce 0 görür.

Alternatifler neredeyse her zaman mevcuttur: değerleri parametre olarak geçirin, sonuç döndürün ve ilişkili durumu açıkça dolaştırılan bir yapıda toplayın. Gerçek istisnalar vardır - program geneline ait bir yapılandırma nesnesi, bir günlükleme tutamacı - ve onlar bile erişimci fonksiyonlarla dosya kapsamında static olarak daha iyidir ki bu sonraki bölümdür.

Dosya Kapsamında static: Gizlilik

Bir küresel değişkende ya da fonksiyonda static, bir yerelde ifade ettiğinden tamamen farklı bir şey ifade eder: iç bağlantı. İsim kendi .c dosyasına özel olur ve başka hiçbir yerden ulaşılamaz.

/* counter.c */
#include "counter.h"

static int count = 0;           /* bu dosyaya özel - başka dosya dokunamaz */

static void validate(void) {    /* özel bir yardımcı, arayüzün parçası değil */
    if (count < 0) count = 0;
}

void increment(void) {          /* açık: counter.h'de bildirilmiş */
    count++;
    validate();
}

int getCount(void) {            /* açık */
    return count;
}

Başka bir dosya increment ve getCount'u çağırabilir, ama count'u göremez ve validate'i çağıramaz - bağlayıcı o isimleri çözmez. C'nin modül sistemi, olduğu kadarıyla budur: açık fonksiyonları bildiren bir başlık ve diğer her şeyde static.

Düzenlilik dışında iki pratik fayda. İki dosyanın her biri, fonksiyonlar açık olsaydı yapamayacakları biçimde, çakışma olmadan birer static void validate(void)'a sahip olabilir. Ve derleyici bir static fonksiyonun dosya dışında çağıranı olmadığını bilir, dolayısıyla onu daha saldırgan biçimde satır içine alabilir ya da kullanılmadığını uyarabilir.

Yani anahtar kelimenin nerede göründüğüne göre belirlenen iki ayrı anlamı vardır:

YEREL bir degiskende static  ->  omur kalici olur (kapsam degismez)
DOSYA kapsaminda static      ->  baglanti ic olur (omur degismez)

Gölgeleme

Dıştaki bir kapsamda zaten var olan bir ismi iç kapsamda bildirmek dıştaki ismi gölgeler: iç kapsamın içinde isim yeni değişkene atıfta bulunur ve dıştaki erişilemez olur.

Bu geçerlidir ve zaman zaman kasıtlıdır. Daha sık bir kazadır ve pahalı bir kaza: bir küreseli güncellemesi gereken bir fonksiyon onun yerine aynı adlı bir yereli günceller ve küresel hiç değişmez. Aynısı bir yerel bir parametreyi gölgelediğinde de olur; girdi için yapmak istediğiniz atama hiçbir yere gitmez.

gcc -Wshadow her durumu bildirir. -Wall içinde değildir, o yüzden açıkça açın:

gcc -Wall -Wextra -Wshadow program.c -o program

Sorundan tamamen kaçınan alışkanlık, tipe göre değil role göre adlandırmaktır: totalScore ile itemScore birbirini gölgeleyemez, oysa ikisi de n adlı iki değişken eninde sonunda gölgeleyecektir.

Hızlı Başvuru

bir blok icinde bildirilen    blok kapsami, otomatik omur, ilk degersizse cop
fonksiyon parametresi         fonksiyon govdesinin blok kapsami, argumanin kopyasi
fonksiyon icinde static       blok kapsami, PROGRAM omru, sifir ilk deger, cagrilar arasi korunur
tum fonksiyonlarin disinda    dosya kapsami, program omru, sifir ilk deger, diger dosyalara gorunur
disarida static               dosya kapsami, program omru, bu .c dosyasina OZEL
extern bildirimi              baska bir dosyada tanimli bir degiskeni adlandirir

Sıkça Sorulan Sorular

C'de kapsam nedir?

Bir ismin görünür olduğu kod bölgesidir. Bir blok (bir çift süslü parantez) içinde bildirilen bir değişken yalnızca o blokta görünürdür - bu blok kapsamıdır. Her fonksiyonun dışında bildirilen birinin dosya kapsamı vardır ve bildiriminden dosyanın sonuna kadar görünürdür.

C'de static bir yerel değişkene ne yapar?

Değişkenin kapsamını değiştirmeden ömrünü değiştirir. Bir static yerel bir kez oluşturulur, bir kez ilk değerini alır ve çağrılar arasında değerini korur, ama yine yalnızca fonksiyonunun içinde görünürdür. Bir küresel kullanmadan bir fonksiyona bellek verme yoludur.

C'de static ile küresel arasındaki fark nedir?

Düz bir küresel, programdaki her dosyaya görünürdür ve diğer dosyalar ona extern ile ulaşabilir. Dosya kapsamındaki bir static değişken ya da fonksiyon o tek .c dosyasına özeldir - diğer dosyalar ona bağlanamaz. Dosya kapsamındaki static gizlemeyle, bir yereldeki static ömürle ilgilidir.

C'de küresel değişkenler neden kötüdür?

Her fonksiyon onları değiştirebilir, dolayısıyla bir hataya programın herhangi bir yerinden yol açılabilir; onları okuyan fonksiyonlar tek başına test edilemez; ve isim tüm programı kaplayarak çakışmalara davetiye çıkarır. Bunun yerine değerleri parametre olarak geçirin ve sonuç döndürün; paylaşılan duruma gerçekten ihtiyaç duyulan yerde onu dosya kapsamında static yapın, böylece ona yalnızca tek bir dosya dokunabilir.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA