Jika di CI tulis:
int num;
Sebelum saya menetapkan apa pun num
, apakah nilai num
tak tentu?
Jika di CI tulis:
int num;
Sebelum saya menetapkan apa pun num
, apakah nilai num
tak tentu?
extern int x;
Namun mendefinisikan selalu berarti mendeklarasikan. Ini tidak benar di C ++, dengan variabel anggota kelas statis seseorang dapat mendefinisikan tanpa mendeklarasikan, karena deklarasi harus dalam definisi kelas (bukan deklarasi!) Dan definisi harus di luar definisi kelas.
Jawaban:
Variabel statis (ruang lingkup file dan fungsi statis) diinisialisasi ke nol:
int x; // zero
int y = 0; // also zero
void foo() {
static int x; // also zero
}
Variabel non-statis (variabel lokal) tidak dapat ditentukan . Membacanya sebelum menetapkan nilai menghasilkan perilaku yang tidak terdefinisi .
void foo() {
int x;
printf("%d", x); // the compiler is free to crash here
}
Dalam praktiknya, mereka cenderung hanya memiliki beberapa nilai yang tidak masuk akal di sana pada awalnya - beberapa kompiler bahkan dapat menempatkan nilai tetap yang spesifik untuk membuatnya jelas saat mencari di debugger - tetapi secara tegas, kompilator bebas melakukan apa pun mulai dari menabrak hingga memanggil setan melalui saluran hidung Anda .
Adapun mengapa ini adalah perilaku tidak terdefinisi alih-alih hanya "nilai tidak ditentukan / sewenang-wenang", ada sejumlah arsitektur CPU yang memiliki bit bendera tambahan dalam representasi mereka untuk berbagai jenis. Contoh modern adalah Itanium, yang memiliki bit "Not a Thing" dalam registernya ; tentu saja, perancang standar C sedang mempertimbangkan beberapa arsitektur yang lebih tua.
Mencoba untuk bekerja dengan nilai dengan ini flag bit-bit dapat menghasilkan pengecualian CPU dalam sebuah operasi yang benar-benar tidak harus gagal (misalnya, penambahan integer, atau menugaskan ke variabel lain). Dan jika Anda pergi dan membiarkan variabel tidak diinisialisasi, compiler mungkin mengambil beberapa sampah acak dengan set bit flag ini - yang berarti menyentuh variabel yang tidak diinisialisasi itu mungkin mematikan.
char
; semua yang lain dapat memiliki representasi jebakan. Atau - karena mengakses variabel yang tidak diinisialisasi adalah UB - kompiler yang sesuai mungkin hanya melakukan beberapa pemeriksaan dan memutuskan untuk memberi sinyal masalah.
C selalu sangat spesifik tentang nilai awal objek. Jika global atau static
, mereka akan menjadi nol. Jika auto
, nilainya tidak pasti .
Ini adalah kasus pada penyusun pra-C89 dan ditentukan oleh K&R dan dalam laporan C asli DMR.
Ini adalah kasus di C89, lihat bagian 6.5.7 Inisialisasi .
Jika objek yang memiliki durasi penyimpanan otomatis tidak diinisialisasi secara eksplisit, nilainya tidak dapat ditentukan. Jika sebuah objek yang memiliki durasi penyimpanan statis tidak diinisialisasi secara eksplisit, itu diinisialisasi secara implisit seolah-olah setiap anggota yang memiliki tipe aritmatika ditugaskan 0 dan setiap anggota yang memiliki tipe pointer diberi konstanta pointer nol.
Ini adalah kasus di C99, lihat bagian 6.7.8 Inisialisasi .
Jika sebuah objek yang memiliki durasi penyimpanan otomatis tidak diinisialisasi secara eksplisit, nilainya tidak dapat ditentukan. Jika sebuah objek yang memiliki durasi penyimpanan statis tidak diinisialisasi secara eksplisit, maka:
- jika memiliki tipe penunjuk, ia diinisialisasi ke penunjuk null;
- jika memiliki tipe aritmatika, itu diinisialisasi ke nol (positif atau unsigned);
- jika merupakan agregat, setiap anggota diinisialisasi (secara rekursif) sesuai dengan aturan ini;
- jika itu adalah sebuah serikat pekerja, nama anggota pertama diinisialisasi (secara rekursif) sesuai dengan aturan ini.
Mengenai apa sebenarnya arti tak tentu , saya tidak yakin untuk C89, C99 mengatakan:
3.17.2
nilai tak tentu
baik nilai tak ditentukan atau representasi perangkap
Tetapi terlepas dari apa yang dikatakan standar, dalam kehidupan nyata, setiap halaman tumpukan sebenarnya dimulai sebagai nol, tetapi ketika program Anda melihat auto
nilai kelas penyimpanan apa pun , ia melihat apa pun yang tertinggal oleh program Anda sendiri saat terakhir kali menggunakan alamat tumpukan tersebut. Jika Anda mengalokasikan banyak auto
larik, Anda akan melihatnya pada akhirnya mulai rapi dengan nol.
Anda mungkin bertanya-tanya, mengapa demikian? Jawaban SO yang berbeda berkaitan dengan pertanyaan itu, lihat: https://stackoverflow.com/a/2091505/140740
indeterminate value
dapat ditemukan di 3.19.2.
Itu tergantung pada durasi penyimpanan variabel. Variabel dengan durasi penyimpanan statis selalu secara implisit diinisialisasi dengan nol.
Sedangkan untuk variabel otomatis (lokal), variabel yang tidak diinisialisasi memiliki nilai yang tidak dapat ditentukan . Nilai tak tentu, antara lain, berarti bahwa "nilai" apa pun yang mungkin "Anda lihat" dalam variabel itu tidak hanya tidak dapat diprediksi, bahkan tidak dijamin akan stabil . Misalnya, dalam prakteknya (yaitu mengabaikan UB sebentar) kode ini
int num;
int a = num;
int b = num;
tidak menjamin variabel tersebut a
dan b
akan menerima nilai yang identik. Menariknya, ini bukanlah konsep teoretis yang terlalu mendasari, ini langsung terjadi dalam praktik sebagai konsekuensi dari pengoptimalan.
Jadi secara umum, jawaban populer bahwa "ia diinisialisasi dengan sampah apa pun yang ada di memori" bahkan tidak benar. Perilaku variabel yang tidak diinisialisasi berbeda dengan variabel yang diinisialisasi dengan sampah.
Contoh Ubuntu 15.10, Kernel 4.2.0, x86-64, GCC 5.2.1
Standar yang cukup, mari kita lihat implementasinya :-)
Variabel lokal
Standar: perilaku tidak terdefinisi.
Implementasi: program mengalokasikan ruang tumpukan, dan tidak pernah memindahkan apa pun ke alamat itu, jadi apa pun yang ada sebelumnya digunakan.
#include <stdio.h>
int main() {
int i;
printf("%d\n", i);
}
kompilasi dengan:
gcc -O0 -std=c99 a.c
keluaran:
0
dan mendekompilasi dengan:
objdump -dr a.out
untuk:
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
Dari pengetahuan kami tentang konvensi panggilan x86-64:
%rdi
adalah argumen printf pertama, dengan demikian string "%d\n"
di alamat0x4005e4
%rsi
adalah argumen printf kedua i
.
Itu berasal dari -0x4(%rbp)
, yang merupakan variabel lokal 4-byte pertama.
Pada titik ini, rbp
di halaman pertama tumpukan telah dialokasikan oleh kernel, jadi untuk memahami nilai itu kita akan melihat ke dalam kode kernel dan mencari tahu apa yang ditetapkan untuk itu.
TODO apakah kernel menyetel memori itu ke sesuatu sebelum menggunakannya kembali untuk proses lain ketika suatu proses mati? Jika tidak, proses baru akan dapat membaca memori program selesai lainnya, membocorkan data. Lihat: Apakah nilai yang tidak diinisialisasi pernah menjadi risiko keamanan?
Kami kemudian juga dapat bermain dengan modifikasi tumpukan kami sendiri dan menulis hal-hal menyenangkan seperti:
#include <assert.h>
int f() {
int i = 13;
return i;
}
int g() {
int i;
return i;
}
int main() {
f();
assert(g() == 13);
}
Variabel lokal di -O3
Analisis implementasi pada: Apa arti <value dioptimalkan out> di gdb?
Variabel global
Standar: 0
Implementasi: .bss
bagian.
#include <stdio.h>
int i;
int main() {
printf("%d\n", i);
}
gcc -00 -std=c99 a.c
mengkompilasi ke:
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>
mengatakan itu i
di alamat 0x601044
dan:
readelf -SW a.out
mengandung:
[25] .bss NOBITS 0000000000601040 001040 000008 00 WA 0 0 4
yang mengatakan 0x601044
tepat di tengah .bss
bagian, yang dimulai pada 0x601040
dan panjangnya 8 byte.
Standar ELF kemudian menjamin bahwa bagian yang dinamai .bss
benar-benar diisi dengan angka nol:
.bss
Bagian ini menyimpan data yang tidak diinisialisasi yang berkontribusi pada gambar memori program. Menurut definisi, sistem menginisialisasi data dengan nol saat program mulai dijalankan. Bagian tidak menggunakan ruang file, seperti yang ditunjukkan oleh tipe bagianSHT_NOBITS
,.
Selain itu, jenisnya SHT_NOBITS
efisien dan tidak menempati ruang pada file yang dapat dieksekusi:
sh_size
Anggota ini memberikan ukuran bagian dalam byte. Kecuali jika tipeSHT_NOBITS
bagiannya adalah , bagian tersebut menempatish_size
byte dalam file. Bagian tipeSHT_NOBITS
mungkin memiliki ukuran bukan nol, tetapi tidak menempati ruang di file.
Kemudian terserah pada kernel Linux untuk mengosongkan wilayah memori tersebut saat memuat program ke dalam memori saat program dimulai.
Itu tergantung. Jika definisi tersebut global (di luar fungsi apa pun) maka num
akan diinisialisasi ke nol. Jika itu lokal (di dalam fungsi) maka nilainya tidak dapat ditentukan. Secara teori, bahkan mencoba membaca nilai memiliki perilaku yang tidak ditentukan - C memungkinkan kemungkinan bit yang tidak berkontribusi pada nilai, tetapi harus disetel dengan cara tertentu agar Anda bahkan mendapatkan hasil yang ditentukan dari membaca variabel.
Karena komputer memiliki kapasitas penyimpanan yang terbatas, variabel otomatis biasanya akan disimpan dalam elemen penyimpanan (baik register atau RAM) yang sebelumnya telah digunakan untuk tujuan arbitrer lainnya. Jika variabel seperti itu digunakan sebelum nilai diberikan padanya, penyimpanan itu dapat menampung apa pun yang dipegangnya sebelumnya, sehingga konten variabel tidak dapat diprediksi.
Sebagai tambahan, banyak kompiler mungkin menyimpan variabel dalam register yang lebih besar dari tipe terkait. Meskipun kompiler akan diminta untuk memastikan bahwa nilai apa pun yang ditulis ke variabel dan dibaca kembali akan dipotong dan / atau diperpanjang tanda ke ukuran yang tepat, banyak kompiler akan melakukan pemotongan seperti itu ketika variabel ditulis dan berharap itu akan terjadi. dilakukan sebelum variabel dibaca. Pada kompiler seperti itu, seperti:
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);
}
mungkin menghasilkan wow()
penyimpanan nilai 1234567 ke dalam register 0 dan 1, dan memanggil foo()
. Karena x
tidak diperlukan dalam "foo", dan karena fungsi seharusnya meletakkan nilai kembaliannya ke register 0, kompilator dapat mengalokasikan register 0 ke q
. Jika mode
1 atau 3, register 0 akan dimuat dengan 2 atau 4, masing-masing, tetapi jika itu adalah nilai lain, fungsi dapat mengembalikan apa pun yang ada di register 0 (yaitu nilai 1234567) meskipun nilai itu tidak dalam kisaran dari uint16_t.
Untuk menghindari keharusan compiler melakukan pekerjaan ekstra guna memastikan bahwa variabel yang tidak diinisialisasi sepertinya tidak pernah menyimpan nilai di luar domain mereka, dan menghindari keharusan untuk menentukan perilaku tak tentu secara berlebihan, Standar mengatakan bahwa penggunaan variabel otomatis yang tidak diinisialisasi adalah Undefined Behavior. Dalam beberapa kasus, konsekuensi dari hal ini bahkan mungkin lebih mengejutkan daripada nilai yang berada di luar kisaran jenisnya. Misalnya, diberikan:
void moo(int mode)
{
if (mode < 5)
launch_nukes();
hey(0, mode);
}
kompiler dapat menyimpulkan bahwa karena memanggil moo()
dengan mode yang lebih besar dari 3 pasti akan menyebabkan program memanggil Undefined Behavior, kompilator dapat menghilangkan kode apa pun yang hanya relevan jika mode
4 atau lebih besar, seperti kode yang biasanya mencegah peluncuran nuklir dalam kasus seperti itu. Perhatikan bahwa baik Standard, maupun filosofi compiler modern, akan peduli dengan fakta bahwa nilai yang dikembalikan dari "hey" diabaikan - tindakan mencoba mengembalikannya memberikan lisensi compiler tak terbatas untuk menghasilkan kode arbitrer.
Jawaban dasarnya adalah, ya itu tidak ditentukan.
Jika Anda melihat perilaku aneh karena ini, mungkin tergantung di mana ia dideklarasikan. Jika di dalam suatu fungsi di stack maka isinya kemungkinan besar akan berbeda setiap kali fungsi dipanggil. Jika itu adalah ruang lingkup statis atau modul, itu tidak ditentukan tetapi tidak akan berubah.
Jika kelas penyimpanan statis atau global maka selama pemuatan, BSS menginisialisasi variabel atau lokasi memori (ML) ke 0 kecuali variabel awalnya diberi nilai tertentu. Dalam kasus variabel lokal yang tidak diinisialisasi, representasi perangkap ditetapkan ke lokasi memori. Jadi jika salah satu register Anda yang berisi info penting ditimpa oleh compiler, program tersebut mungkin macet.
tetapi beberapa kompiler mungkin memiliki mekanisme untuk menghindari masalah seperti itu.
Saya bekerja dengan seri nec v850 ketika saya menyadari Ada representasi perangkap yang memiliki pola bit yang mewakili nilai yang tidak ditentukan untuk tipe data kecuali untuk char. Ketika saya mengambil karakter yang tidak diinisialisasi saya mendapat nilai default nol karena representasi perangkap. Ini mungkin berguna untuk any1 yang menggunakan necv850es
Nilai num adalah nilai sampah dari memori utama (RAM). lebih baik jika Anda menginisialisasi variabel segera setelah membuat.
Sejauh yang saya lakukan itu sebagian besar tergantung pada kompiler tetapi secara umum sebagian besar kasus nilai sebelumnya diasumsikan sebagai 0 oleh pelengkap.
Saya mendapat nilai sampah dalam kasus VC ++ sementara TC memberi nilai 0. Saya mencetaknya seperti di bawah ini
int i;
printf('%d',i);
0
kompilator Anda kemungkinan besar akan melakukan langkah-langkah ekstra untuk memastikan nilai tersebut (dengan menambahkan kode untuk menginisialisasi variabel). Beberapa kompiler melakukan ini saat melakukan kompilasi "debug", tetapi memilih nilai 0
untuk ini adalah ide yang buruk karena akan menyembunyikan kesalahan dalam kode Anda (hal yang lebih tepat akan menjamin angka yang sangat tidak mungkin seperti 0xBAADF00D
atau yang serupa). Saya pikir sebagian besar kompilator hanya akan meninggalkan sampah apa pun yang terjadi untuk menempati memori sebagai nilai variabel (mis. Itu secara umum tidak diasumsikan sebagai 0
).