Dalam bahasa C, jika menginisialisasi array seperti ini:
int a[5] = {1,2};
maka semua elemen dari array yang tidak diinisialisasi secara eksplisit akan diinisialisasi secara implisit dengan nol.
Tapi, jika saya menginisialisasi array seperti ini:
int a[5]={a[2]=1};
printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
keluaran:
1 0 1 0 0
Saya tidak mengerti, mengapa a[0]
mencetak, 1
bukan 0
? Apakah ini perilaku yang tidak terdefinisi?
Catatan: Pertanyaan ini ditanyakan dalam sebuah wawancara.
a[2]=1
terevaluasi menjadi1
.a[2] = 1
tersebut1
, tetapi saya tidak yakin apakah Anda diizinkan untuk mengambil hasil dari ekspresi penginisialisasi yang ditetapkan sebagai nilai elemen pertama. Fakta bahwa Anda telah menambahkan tanda pengacara berarti saya pikir kita membutuhkan jawaban yang mengutip standar.Jawaban:
TL; DR: Saya rasa perilaku dari
int a[5]={a[2]=1};
didefinisikan dengan baik, setidaknya di C99.Bagian lucunya adalah bahwa satu-satunya bit yang masuk akal bagi saya adalah bagian yang Anda tanyakan:
a[0]
disetel ke1
karena operator penugasan mengembalikan nilai yang telah ditetapkan. Segala sesuatu yang lain tidak jelas.Jika kodenya adalah
int a[5] = { [2] = 1 }
, semuanya akan mudah: Itu adalah pengaturan penginisialisasi yang ditunjuka[2]
untuk1
dan yang lainnya ke0
. Tapi dengan{ a[2] = 1 }
kita memiliki penginisialisasi yang tidak ditunjuk yang berisi ekspresi tugas, dan kita jatuh ke lubang kelinci.Inilah yang saya temukan sejauh ini:
a
harus berupa variabel lokal.a[2] = 1
bukan ekspresi konstan, jadia
harus memiliki penyimpanan otomatis.a
berada dalam ruang lingkup inisialisasi sendiri.Deklaratornya adalah
a[5]
, jadi variabel berada dalam lingkup inisialisasi mereka sendiri.a
masih hidup dalam inisialisasi sendiri.Ada titik urutan setelahnya
a[2]=1
.Perhatikan bahwa misalnya dalam
int foo[] = { 1, 2, 3 }
satu{ 1, 2, 3 }
bagian adalah daftar penjepit tertutup dari initializers, yang masing-masing memiliki titik urutan setelah.Inisialisasi dilakukan dalam urutan daftar penginisialisasi.
Namun, ekspresi penginisialisasi tidak selalu dievaluasi secara berurutan.
Namun, masih ada beberapa pertanyaan yang belum terjawab:
Apakah poin urutan bahkan relevan? Aturan dasarnya adalah:
a[2] = 1
adalah ekspresi, tetapi inisialisasi bukan.Ini sedikit bertentangan dengan Lampiran J:
Lampiran J mengatakan setiap modifikasi dihitung, tidak hanya modifikasi oleh ekspresi. Tetapi mengingat lampiran itu non-normatif, kita mungkin dapat mengabaikannya.
Bagaimana urutan inisialisasi subobjek sehubungan dengan ekspresi penginisialisasi? Apakah semua penginisialisasi dievaluasi terlebih dahulu (dalam beberapa urutan), kemudian subobjek diinisialisasi dengan hasil (dalam urutan daftar penginisialisasi)? Atau bisakah mereka disisipkan?
Saya pikir
int a[5] = { a[2] = 1 }
dieksekusi sebagai berikut:a
dialokasikan ketika blok berisi dimasukkan. Isinya tidak dapat ditentukan pada saat ini.a[2] = 1
), diikuti dengan titik urutan. Ini toko1
dia[2]
dan kembali1
.1
digunakan untuk menginisialisasia[0]
(penginisialisasi pertama menginisialisasi subobjek pertama).Tapi di sini hal-hal kabur karena unsur-unsur yang tersisa (
a[1]
,a[2]
,a[3]
,a[4]
) seharusnya diinisialisasi untuk0
, tetapi tidak jelas kapan: Apakah itu terjadi sebeluma[2] = 1
dievaluasi? Jika demikian,a[2] = 1
akankah "menang" dan menimpaa[2]
, tetapi apakah tugas tersebut memiliki perilaku yang tidak ditentukan karena tidak ada titik urutan antara inisialisasi nol dan ekspresi tugas? Apakah poin urutan bahkan relevan (lihat di atas)? Atau apakah nol inisialisasi terjadi setelah semua penginisialisasi dievaluasi? Jika demikian,a[2]
harus berakhir menjadi0
.Karena standar C tidak secara jelas mendefinisikan apa yang terjadi di sini, saya yakin perilakunya tidak ditentukan (oleh kelalaian).
sumber
a[0]
subobjek sebelum mengevaluasi penginisialisasinya, dan mengevaluasi penginisialisasi apa pun menyertakan titik urutan (karena ini adalah "ekspresi penuh"). Oleh karena itu, saya yakin memodifikasi subobjek yang kami inisialisasi adalah permainan yang adil.Agaknya
a[2]=1
menginisialisasia[2]
terlebih dahulu, dan hasil ekspresi digunakan untuk menginisialisasia[0]
.Dari N2176 (draft C17):
Jadi nampaknya keluaran
1 0 0 0 0
juga akan dimungkinkan.Kesimpulan: Jangan menulis penginisialisasi yang mengubah variabel yang diinisialisasi dengan cepat.
sumber
{...}
ekspresi yang menginisialisasia[2]
ke0
, dana[2]=1
sub-ekspresi yang menginisialisasia[2]
ke1
.{...}
adalah daftar penginisialisasi yang diperkuat. Itu bukan ekspresi.Saya pikir standar C11 mencakup perilaku ini dan mengatakan bahwa hasilnya tidak ditentukan , dan menurut saya C18 tidak membuat perubahan apa pun yang relevan di area ini.
Bahasa standar tidak mudah diurai. Bagian yang relevan dari standar adalah §6.7.9 Inisialisasi . Sintaksnya didokumentasikan sebagai:
Perhatikan bahwa salah satu termnya adalah assignment-expression , dan karena itu
a[2] = 1
adalah ekspresi assignment, itu diperbolehkan di dalam penginisialisasi untuk array dengan durasi non-statis:Salah satu paragraf kuncinya adalah:
Dan paragraf kunci lainnya adalah:
Saya cukup yakin bahwa paragraf §23 menunjukkan bahwa notasi dalam pertanyaan:
mengarah ke perilaku yang tidak ditentukan. Penugasan ke
a[2]
adalah efek samping, dan urutan evaluasi ekspresi diurutkan secara tidak pasti terkait satu sama lain. Akibatnya, menurut saya tidak ada cara untuk menarik standar dan mengklaim bahwa kompilator tertentu menangani ini dengan benar atau salah.sumber
Pemahaman saya
a[2]=1
mengembalikan nilai 1 jadi kode menjadiint a[5]={1}
berikan nilai untuk a [0] = 1Karenanya mencetak 1 untuk [0]
Sebagai contoh
sumber
Saya mencoba memberikan jawaban singkat dan sederhana untuk teka-teki tersebut:
int a[5] = { a[2] = 1 };
a[2] = 1
diatur. Itu berarti array mengatakan:0 0 1 0 0
{ }
tanda kurung, yang digunakan untuk menginisialisasi array secara berurutan, ia mengambil nilai pertama (yaitu1
) dan menyetelnya kea[0]
. Seolah-olahint a[5] = { a[2] };
akan tetap, di mana kita sudah mendapatkannyaa[2] = 1
. Array yang dihasilkan sekarang adalah:1 0 1 0 0
Contoh lain:
int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };
- Meskipun urutannya agak sewenang-wenang, dengan asumsi itu bergerak dari kiri ke kanan, itu akan berjalan dalam 6 langkah berikut:sumber
A = B = C = 5
bukan deklarasi (atau inisialisasi). Ini adalah ekspresi normal yang diuraiA = (B = (C = 5))
karena=
operatornya adalah asosiatif yang benar. Itu tidak terlalu membantu menjelaskan cara kerja inisialisasi. Array sebenarnya mulai ada ketika blok yang didefinisikan di dalamnya dimasukkan, yang bisa lama sebelum definisi sebenarnya dieksekusi.a[2] = 1
ekspresi penginisialisasi diterapkan? Hasil yang diamati adalah seolah-olah, tetapi standar tampaknya tidak menentukan bahwa seharusnya demikian. Itulah pusat kontroversi, dan jawaban ini sepenuhnya mengabaikannya.Tugas
a[2]= 1
adalah ekspresi yang memiliki nilai1
, dan pada dasarnya Anda menulisint a[5]= { 1 };
(dengan efek samping yanga[2]
ditetapkan1
juga).sumber
Aku percaya itu
int a[5]={ a[2]=1 };
adalah contoh yang baik bagi seorang programmer yang menembak dirinya sendiri ke kakinya sendiri.Saya mungkin tergoda untuk berpikir bahwa yang Anda maksud adalah
int a[5]={ [2]=1 };
yang akan menjadi elemen pengaturan penginisialisasi C99 yang ditunjuk 2 ke 1 dan sisanya ke nol.Dalam kasus yang jarang terjadi yang benar-benar Anda maksudkan
int a[5]={ 1 }; a[2]=1;
, itu akan menjadi cara penulisan yang lucu. Bagaimanapun, inilah inti dari kode Anda, meskipun beberapa di sini menunjukkan bahwa itu tidak didefinisikan dengan baik ketika penulisan kea[2]
benar-benar dijalankan. Perangkap di sini adalah bahwaa[2]=1
bukan penginisialisasi yang ditunjuk tetapi tugas sederhana yang dengan sendirinya memiliki nilai 1.sumber