C'de bildirilen, başlatılmamış bir değişkene ne olur? Bir değeri var mı?


139

CI yazıyorsanız:

int num;

Bir şey atamadan önce num, değeri numbelirsiz midir?


4
Bu tanımlanmış bir değişken değil, beyan edilen bir değişken değil mi? (Bu benim C ++ parlıyor ...)
sbi

6
Hayır. Bir değişkeni tanımlamaksızın bildirebilirim: extern int x;Ancak tanımlamak her zaman bildirmeyi gerektirir. Bu, C ++ için geçerli değildir, statik sınıf üyesi değişkenleri ile, bildirim sınıf tanımında (bildirim değil!) Ve tanım sınıf tanımının dışında olmalıdır.
bdonlan

ee.hawaii.edu/~tep/EE160/Book/chap14/subsection2.1.1.4.html Tanımlı gibi görünüyor, onu da başlatmanız gerektiği anlamına geliyor.
atp

Yanıtlar:


188

Statik değişkenler (dosya kapsamı ve işlev statik) sıfıra başlatılır:

int x; // zero
int y = 0; // also zero

void foo() {
    static int x; // also zero
}

Statik olmayan değişkenler (yerel değişkenler) belirsizdir . Bir değer atamadan önce bunları okumak, tanımlanmamış davranışla sonuçlanır.

void foo() {
    int x;
    printf("%d", x); // the compiler is free to crash here
}

Pratikte, başlangıçta orada bazı saçma değerlere sahip olma eğilimindedirler - bazı derleyiciler, bir hata ayıklayıcıya bakarken bunu belirgin hale getirmek için belirli, sabit değerler bile koyabilirler - ama kesinlikle konuşursak, derleyici çökmekten çağrılmaya kadar her şeyi yapabilir. burun pasajlarınızdan iblisler .

Neden sadece "tanımlanmamış / keyfi değer" yerine tanımlanmamış davranışa gelince, çeşitli türler için temsillerinde ek bayrak bitleri olan birkaç CPU mimarisi vardır. Modern bir örnek , kayıtlarında "Şey Değil" biti olan Itanium ; elbette, C standart taslakları bazı eski mimarileri düşünüyorlardı.

Bu işaret bitleri ayarlanmış bir değerle çalışmaya çalışmak, gerçekten başarısız olmaması gereken bir işlemde CPU istisnasına neden olabilir (örneğin, tamsayı ekleme veya başka bir değişkene atama). Ve gidip bir değişkeni başlatılmamış bırakırsanız, derleyici bu bayrak bitleri kümesiyle bazı rastgele çöpler alabilir - yani başlatılmamış değişkene dokunmak ölümcül olabilir.


2
oh hayır değiller. Hata ayıklama modunda, bir müşterinin önünde olmadığınızda, bir R in ile aylarda, şanslıysanız olabilirler
Martin Beckett

8
ne değil? statik başlatma standart tarafından gereklidir; bkz ISO / IEC 9899: 1999 6.7.8 # 10
bdonlan

2
ilk örnek anlayabildiğim kadarıyla iyi. Derleyici neden ikincisinde çökebilir daha az :)

6
@Stuart: "bindirme temsili" adı verilen ve temelde geçerli bir değeri göstermeyen bir bit örüntüsü olan ve örneğin çalışma zamanında donanım istisnalarına neden olabilecek bir şey var. Herhangi bir bit paterninin geçerli bir değer olduğunu garanti eden tek C türü char; diğerlerinin hepsinde tuzak temsili olabilir. Alternatif olarak - başlatılmamış değişkene erişim yine de UB olduğundan - uygun bir derleyici sadece bazı kontroller yapabilir ve sorunu işaret etmeye karar verebilir.
Pavel Minaev

5
bdonian doğrudur. C her zaman oldukça kesin olarak belirtilmiştir. C89 ve C99'dan önce, dmr'nin bir makalesi tüm bunları 1970'lerin başında belirtmiştir. En kabaca gömülü sistemde bile, işleri doğru yapmak için sadece bir memset () gerekir, bu nedenle uygun olmayan bir ortam için mazeret yoktur. Cevabımda standardı belirtmiştim.
DigitalRoss

57

Statik veya global ise 0, depolama sınıfı otomatikse belirsiz

C, nesnelerin başlangıç ​​değerleri hakkında her zaman çok spesifik olmuştur. Eğer küresel veya staticsıfırlanırlar. Eğer auto, değer belirsizdir .

C89 öncesi derleyicilerde durum böyleydi ve K&R ve DMR'nin orijinal C raporunda belirtildi.

C89'da durum böyleydi, bkz. Bölüm 6.5.7 Başlatma .

Otomatik saklama süresi olan bir nesne açık bir şekilde başlatılmazsa değeri belirsizdir. Statik depolama süresi olan bir nesneye açık bir şekilde başlatılmazsa, aritmetik tipte olan her üyeye 0 ve işaretçi tipine sahip her üyeye boş bir işaretçi sabiti atanmış gibi uygulanır.

C99'da durum böyleydi, bkz. Bölüm 6.7.8 Başlatma .

Otomatik saklama süresi olan bir nesne açıkça başlatılmazsa değeri belirsizdir. Statik depolama süresi olan bir nesne açıkça başlatılmazsa, o zaman:
- işaretçi türüne sahipse, boş gösterici olarak başlatılır;
- aritmetik tipte ise sıfır (pozitif veya işaretsiz) olarak sıfırlanır;
- eğer bir toplu ise, her üye bu kurallara göre (özyineli olarak) başlatılır;
- eğer sendika ise, ilk adlandırılan üye bu kurallara göre (özyineli olarak) başlatılır.

Tam olarak ne gelince belirsiz yollarla, ben C89 için emin değilim, C99 diyor ki:

3.17.2
Belirsiz değer

ya belirtilmemiş bir değer ya da bir tuzak temsili

Ancak standartların ne söylediğine bakılmaksızın, gerçek hayatta her yığın sayfası aslında sıfır olarak başlar, ancak programınız herhangi bir autodepolama sınıfı değerine baktığında, bu yığın adreslerini en son kullandığı zaman kendi programınız tarafından geride kalanları görür. Çok sayıda autodizi ayırırsanız, bunların sonunda sıfırlarla düzgün bir şekilde başladığını göreceksiniz.

Merak edebilirsiniz, neden bu şekilde? Farklı bir SO yanıtı bu soru ile ilgilidir, bkz: https://stackoverflow.com/a/2091505/140740


3
belirsiz (genellikle?) her şeyi yapabileceği anlamına gelir. Sıfır olabilir, orada bulunan değer olabilir, programı çökertebilir, bilgisayarın CD yuvasından yabanmersini krep üretmesini sağlayabilir. kesinlikle hiçbir garantiniz yok. Gezegenin yok olmasına neden olabilir. En azından spec kadarıyla ... aslında böyle bir şey yapan bir derleyici yapan herkes B- üzerine çok kaşlarını çattı)
Brian Postow

C11 N1570 taslağında, tanımı indeterminate value3.19.2'de bulunabilir.
user3528438

Statik değişken için ayarladığı değerin her zaman derleyiciye veya işletim sistemine bağlı olması mı? Örneğin, birisi kendi işletim sistemimi veya derleyiciyi yazarsa ve statik için belirsiz olarak başlangıç ​​değerini varsayılan olarak belirlerse, bu mümkün müdür?
Aditya Singh

1
@AdityaSingh, işletim sistemi derleyiciyi daha kolay hale getirebilir , ancak sonuç olarak dünyanın mevcut C kodu kataloğunu çalıştırmak derleyicinin birincil sorumluluğudur ve standartları karşılamak için ikincil bir sorumluluktur. Farklı bir şekilde yapmak kesinlikle mümkün olurdu , ama neden? OS çünkü Ayrıca, statik veri belirsiz hale getirmek için yanıltıcı olabileceğini gerçekten güvenlik nedeniyle ilk sayfalarını sıfır istiyoruz. (Otomatik değişkenler sadece yüzeysel olarak tahmin edilemez çünkü kendi programınız genellikle bu yığın adreslerini daha erken bir noktada kullanıyor.)
DigitalRoss

@BrianPostow Hayır, bu doğru değil. Bkz. Stackoverflow.com/a/40674888/584518 . Belirsiz bir değer kullanılması , tuzak gösterimleri durumunda kaydedilen tanımlanmamış davranışa değil, belirtilmemiş davranışa neden olur .
Lundin

12

Değişkenin saklama süresine bağlıdır. Statik depolama süresine sahip bir değişken her zaman örtük olarak sıfır ile başlatılır.

Otomatik (yerel) değişkenlere gelince, başlatılmamış bir değişken belirsiz bir değere sahiptir . Belirsiz değer, diğer şeylerin yanı sıra, bu değişkende "görebileceğiniz" değer "ne olursa olsun, sadece öngörülemez olmakla kalmaz, aynı zamanda kararlı olduğu bile garanti edilmez . Örneğin, pratikte (yani UB'yi bir saniyeliğine yoksaymak) bu kod

int num;
int a = num;
int b = num;

değişkenleri garanti etmez ave baynı değerleri alır. İlginçtir, bu bazı bilgiçliksel teorik bir kavram değildir, bu pratikte optimizasyonun bir sonucu olarak kolayca gerçekleşir.

Bu nedenle, genel olarak, "bellekte ne olursa olsun çöp ile başlatılır" diye verilen popüler cevap uzaktan bile doğru değildir. Başlatılmamış değişkenin davranışı, çöple başlatılan bir değişkenin davranışından farklıdır .


Anlayamıyorum (iyi çok iyi can D: Bu sadece bir dakika sonra DigitalRoss gelen olandan çok daha az upvotes sahip olmasının)
Antti Haapala

7

Ubuntu 15.10, Çekirdek 4.2.0, x86-64, GCC 5.2.1 örneği

Yeterli standart, bir uygulamaya bakalım :-)

Yerel değişken

Standartlar: tanımlanmamış davranış.

Uygulama: Program yığın alanı ayırır ve hiçbir zaman bu adrese hiçbir şey taşımaz, bu nedenle daha önce ne varsa kullanılır.

#include <stdio.h>
int main() {
    int i;
    printf("%d\n", i);
}

derlemek:

gcc -O0 -std=c99 a.c

çıktılar:

0

ve aşağıdakilerle ayrışır:

objdump -dr a.out

için:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       48 83 ec 10             sub    $0x10,%rsp
  40053e:       8b 45 fc                mov    -0x4(%rbp),%eax
  400541:       89 c6                   mov    %eax,%esi
  400543:       bf e4 05 40 00          mov    $0x4005e4,%edi
  400548:       b8 00 00 00 00          mov    $0x0,%eax
  40054d:       e8 be fe ff ff          callq  400410 <printf@plt>
  400552:       b8 00 00 00 00          mov    $0x0,%eax
  400557:       c9                      leaveq
  400558:       c3                      retq

X86-64 çağrı sözleşmeleri bilgimizden:

  • %rdiilk printf argümanıdır, dolayısıyla "%d\n"adresteki dize0x4005e4

  • %rsiikinci printf argümanıdır i.

    Bu gelir -0x4(%rbp)ilk 4 baytlık lokal değişken olan.

    Bu noktada, rbpyığının ilk sayfasında çekirdek tarafından tahsis edilmiştir, bu yüzden bu değeri anlamak için çekirdek koduna bakıp neyi ayarladığını öğreneceğiz.

    YAPILACAK Çekirdek, bir işlem öldüğünde diğer işlemler için yeniden kullanmadan önce bu belleği bir şeye ayarlıyor mu? Değilse, yeni süreç veri sızdıran diğer bitmiş programların belleğini okuyabilecektir. Bakınız: Başlatılmamış değerler bir güvenlik riski oluşturuyor mu?

Daha sonra kendi yığın değişikliklerimizle oynayabilir ve aşağıdaki gibi eğlenceli şeyler yazabiliriz:

#include <assert.h>

int f() {
    int i = 13;
    return i;
}

int g() {
    int i;
    return i;
}

int main() {
    f();
    assert(g() == 13);
}

Yerel değişken -O3

Uygulama analizi: gdb'de <optimize edilmiş değer> ne anlama geliyor?

Global değişkenler

Standartlar: 0

Uygulama: .bssbölüm.

#include <stdio.h>
int i;
int main() {
    printf("%d\n", i);
}

gcc -00 -std=c99 a.c

derler:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       8b 05 04 0b 20 00       mov    0x200b04(%rip),%eax        # 601044 <i>
  400540:       89 c6                   mov    %eax,%esi
  400542:       bf e4 05 40 00          mov    $0x4005e4,%edi
  400547:       b8 00 00 00 00          mov    $0x0,%eax
  40054c:       e8 bf fe ff ff          callq  400410 <printf@plt>
  400551:       b8 00 00 00 00          mov    $0x0,%eax
  400556:       5d                      pop    %rbp
  400557:       c3                      retq
  400558:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40055f:       00

# 601044 <i>iadreste olduğunu söylüyor 0x601044ve:

readelf -SW a.out

içerir:

[25] .bss              NOBITS          0000000000601040 001040 000008 00  WA  0   0  4

8 bayt uzunluğunda başlayan bölümün 0x601044tam ortasında diyor ..bss0x601040

ELF standart sonra adlandırılmış bölüm garanti .bsstamamen sıfır ile doldurulur:

.bssBu bölüm, programın bellek görüntüsüne katkıda bulunan başlatılmamış verileri içerir. Tanım olarak, program çalışmaya başladığında sistem verileri sıfırlarla başlatır. Kesit, kesit tipinde belirtildiği gibi dosya alanı içermez SHT_NOBITS.

Ayrıca, tür SHT_NOBITSetkilidir ve yürütülebilir dosyada yer kaplamaz:

sh_sizeBu üye bölümün boyutunu bayt cinsinden verir. SHT_NOBITSBölüm türü olmadığı sürece, bölüm sh_size dosyadaki baytları kaplar . Türün bir bölümü SHT_NOBITSsıfır olmayan bir boyuta sahip olabilir, ancak dosyada yer kaplamaz.

Ardından, programı başlatıldığında belleğe yüklerken bu bellek bölgesini sıfırlamak Linux çekirdeğine bağlıdır.


4

Bu bağlıdır. Bu tanım global ise (herhangi bir fonksiyonun dışında) numsıfıra sıfırlanır. Yerel ise (bir fonksiyonun içinde) değeri belirsizdir. Teorik olarak, değeri okuma girişiminde bile tanımlanmamış davranışlar vardır - C, değere katkıda bulunmayan, ancak değişkeni okumaktan tanımlanmış sonuçlar elde edebilmeniz için belirli yollarla ayarlanması gereken bitlerin olasılığına izin verir.


1

Bilgisayarların sınırlı depolama kapasitesi olduğundan, otomatik değişkenler tipik olarak daha önce başka bir rasgele amaç için kullanılmış olan depolama öğelerinde (kayıtlar veya RAM olsun) tutulur. Böyle bir değişken kendisine bir değer atanmadan önce kullanılırsa, o depolama alanı daha önce tuttuğu her şeyi tutabilir ve bu nedenle değişkenin içeriği önceden kestirilemez.

Ek bir kırışıklık olarak, birçok derleyici değişkenleri ilişkili türlerden daha büyük kayıtlarda tutabilir. Bir derleyiciye, bir değişkene yazılan ve geri okunan herhangi bir değerin kesilecek ve / veya işaretinin uygun boyutuna genişletilmesini sağlamak gerekecek olsa da, birçok derleyici, değişkenler yazıldığında bu kesmeyi gerçekleştirecek ve değişken okunmadan önce gerçekleştirilir. Bu tür derleyicilerde şuna benzer:

uint16_t hey(uint32_t x, uint32_t mode)
{ uint16_t q; 
  if (mode==1) q=2; 
  if (mode==3) q=4; 
  return q; }

 uint32_t wow(uint32_t mode) {
   return hey(1234567, mode);
 }

Çok iyi neden olabilir wow()sırasıyla kayıt 0 ve 1 içine değerleri 1234567 depolanması ve çağrı foo(). Yana xfonksiyonları kayıt 0 onların dönüş değeri koymak gerekiyor çünkü "foo" içinde gerekli değildir ve, derleyici 0 kayıt tahsis edebilir q. Eğer modebu değer aralığı içinde olmasa bile, 1 ya da 3 olduğu, 0 ise 2 ya da 4, ile yüklenir kayıt, ancak başka bir değer ise, fonksiyon 0 kayıt (yani değeri 1234567) ne olursa olsun döndürebilir uint16_t.

Derleyicilerin başlatılmamış değişkenlerin alanlarının dışında hiçbir zaman değerleri tutmadığından emin olmak için fazladan iş yapmalarını önlemek ve belirsiz davranışları aşırı ayrıntılı olarak belirtmek zorunda kalmamak için Standart, başlatılmamış otomatik değişkenlerin kullanımının Tanımlanmamış Davranış olduğunu söylüyor. Bazı durumlarda, bunun sonuçları türünün aralığının dışında bir değerden daha şaşırtıcı olabilir. Örneğin, verilenler:

void moo(int mode)
{
  if (mode < 5)
    launch_nukes();
  hey(0, mode);      
}

bir derleyici, moo()3'ten büyük bir modla çağırmanın kaçınılmaz olarak Tanımsız Davranış'ı çağırmasına neden olacağından, derleyicinin modenormalde önleyecek kod gibi yalnızca 4 veya daha büyükse ilgili olabilecek herhangi bir kodu atlayabileceğini söyleyebilir. bu gibi durumlarda nükleer silahların fırlatılması. Ne Standart ne de modern derleyici felsefesinin, "hey" den dönüş değerinin göz ardı edilmesini umursamayacağına dikkat edin - onu döndürmeye çalışma eylemi, derleyiciye rasgele kod üretmek için sınırsız lisans verir.


0

Temel cevap, evet tanımsız.

Bu nedenle tuhaf davranışlar görüyorsanız, nerede bildirildiğine bağlı olabilir. Yığındaki bir işlev içindeyse, işlev her çağrıldığında içerik büyük olasılıkla farklı olacaktır. Statik veya modül kapsamı varsa tanımsızdır ancak değişmez.


0

Depolama sınıfı statik veya globalse yükleme sırasında BSS , değişkene başlangıçta bir değer atanmadığı sürece değişkeni veya bellek konumunu (ML) 0 olarak başlatır . Yerel başlatılmamış değişkenler durumunda, tuzak temsili bellek konumuna atanır. Bu nedenle, önemli bilgiler içeren kayıtlarınızdan herhangi biri derleyici tarafından üzerine yazılırsa program çökebilir.

ancak bazı derleyiciler böyle bir sorunu önleme mekanizmasına sahip olabilir.

Fark ettiğimde nec v850 serisi ile çalışıyordum Char hariç veri türleri için tanımlanmamış değerleri temsil eden bit desenleri olan tuzak gösterimi var. Başlatılmamış bir karakter aldığımda tuzak temsili nedeniyle sıfır varsayılan değer aldım. Bu necv850es kullanan any1 için yararlı olabilir


İmzasız karakter kullanırken tuzak temsili alırsanız sisteminiz uyumlu değildir. Bunların tuzak temsillerini içermelerine açıkça izin verilmemektedir, C17 6.2.6.1/5.
Lundin

-2

Num değeri, ana bellekten (RAM) bir miktar çöp değeri olacaktır. değişkeni oluşturduktan hemen sonra başlatırsanız daha iyi olur.


-4

Gittiğim kadarıyla çoğunlukla derleyiciye bağlıdır, ancak genel olarak çoğu durumda değer, dengeleyiciler tarafından 0 olarak kabul edilir.
TC 0 olarak değer verirken VC ++ durumunda çöp değeri aldım. Aşağıdaki gibi yazdırıyorum

int i;
printf('%d',i);

Örneğin belirleyici bir değer alırsanız 0, derleyiciniz bu değeri aldığından emin olmak için büyük olasılıkla ek adımlar atar (yine de değişkenleri başlatmak için kod ekleyerek). Bazı derleyiciler "hata ayıklama" derlemesi yaparken bunu yaparlar, ancak 0kodunuzdaki hataları gizleyeceği için bunların değerini seçmek kötü bir fikirdir (daha uygun bir şey, benzeri olmayan bir sayıyı 0xBAADF00Dveya benzer bir şeyi garanti etmek için daha uygun olacaktır ). Ben en derleyici sadece değişkenin değeri olarak bellek işgal etmek ne olursa olsun çöp bırakacaktır düşünüyorum (yani. Genel olarak olarak kabul edilmez 0).
Nisan'da
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.