Apa saja perilaku umum yang tidak terdefinisi yang harus diketahui oleh seorang programmer C ++?
Katakan, seperti:
a[i] = i++;
Apa saja perilaku umum yang tidak terdefinisi yang harus diketahui oleh seorang programmer C ++?
Katakan, seperti:
a[i] = i++;
Jawaban:
NULL
pointermemcpy
untuk menyalin buffer yang tumpang tindih .int64_t i = 1; i <<= 72
ditentukan)int i; i++; cout << i;
)volatile
atau sig_atomic_t
pada saat menerima sinyallong int
#if
ekspresiUrutan yang parameter fungsi dievaluasi adalah perilaku yang tidak ditentukan . (Ini tidak akan membuat program Anda macet, meledak, atau memesan pizza ... tidak seperti perilaku yang tidak terdefinisi .)
Satu-satunya persyaratan adalah bahwa semua parameter harus dievaluasi sepenuhnya sebelum fungsi dipanggil.
Ini:
// The simple obvious one.
callFunc(getA(),getB());
Dapat setara dengan ini:
int a = getA();
int b = getB();
callFunc(a,b);
Atau ini:
int b = getB();
int a = getA();
callFunc(a,b);
Ini bisa berupa; terserah kompiler. Hasilnya bisa berarti, tergantung pada efek sampingnya.
Kompiler bebas untuk memesan ulang bagian evaluasi ekspresi (dengan asumsi artinya tidak berubah).
Dari pertanyaan awal:
a[i] = i++;
// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)
// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:
int rhs = i++;
int lhs& = a[i];
lhs = rhs;
// or
int lhs& = a[i];
int rhs = i++;
lhs = rhs;
Penguncian ganda diperiksa. Dan satu kesalahan mudah dibuat.
A* a = new A("plop");
// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'
// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.
// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
a = new A("Plop"); // (Point A).
}
}
a->doStuff();
// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
// Remember (c) has been done thus 'a' is not NULL.
// But the memory has not been initialized.
// Thread 2 now executes doStuff() on an uninitialized variable.
// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
A* tmp = new A("Plop"); // (Point A).
a = tmp;
}
}
a->doStuff();
// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.
Favorit saya adalah "rekursi tak terbatas dalam instantiasi templat" karena saya percaya itu satu-satunya di mana perilaku tidak terdefinisi terjadi pada waktu kompilasi.
Selain perilaku tidak terdefinisi , ada juga perilaku implementasi-didefinisikan sama jahatnya .
Perilaku tidak terdefinisi terjadi ketika suatu program melakukan sesuatu yang hasilnya tidak ditentukan oleh standar.
Perilaku yang didefinisikan oleh implementasi adalah tindakan oleh suatu program yang hasilnya tidak ditentukan oleh standar, tetapi implementasi tersebut diperlukan untuk didokumentasikan. Contohnya adalah "Multibyte karakter literal", dari pertanyaan Stack Overflow Apakah ada kompiler C yang gagal mengkompilasi ini? .
Perilaku yang ditentukan oleh implementasi hanya menggigit Anda ketika Anda memulai porting (tetapi memutakhirkan ke versi baru dari compiler juga porting!)
Variabel hanya dapat diperbarui sekali dalam ekspresi (secara teknis satu kali antara titik urutan).
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
Pemahaman dasar tentang berbagai batasan lingkungan. Daftar lengkapnya ada di bagian 5.2.4.1 dari spesifikasi C. Berikut beberapa di antaranya;
Saya sebenarnya sedikit terkejut dengan batas 1023 label kasus untuk pernyataan switch, saya bisa melihat bahwa terlampaui untuk kode yang dihasilkan / lex / parser cukup mudah.
Jika batas ini terlampaui, Anda memiliki perilaku yang tidak terdefinisi (crash, cacat keamanan, dll ...).
Benar, saya tahu ini dari spesifikasi C, tetapi C ++ membagikan dukungan dasar ini.
Menggunakan memcpy
untuk menyalin antara wilayah memori yang tumpang tindih. Sebagai contoh:
char a[256] = {};
memcpy(a, a, sizeof(a));
Perilaku tidak terdefinisi menurut Standar C, yang digolongkan oleh Standar C ++ 03.
Ringkasan
1 / # sertakan void * memcpy (void * batasi s1, const void * batasi s2, size_t n);
Deskripsi
2 / Fungsi memcpy menyalin n karakter dari objek yang ditunjuk oleh s2 ke objek yang ditunjuk oleh s1. Jika penyalinan terjadi di antara objek yang tumpang tindih, perilaku tidak terdefinisi. Returns 3 Fungsi memcpy mengembalikan nilai s1.
Ringkasan
1 # sertakan void * memmove (void * s1, const void * s2, size_t n);
Deskripsi
2 Fungsi memmove menyalin n karakter dari objek yang ditunjuk oleh s2 ke objek yang ditunjuk oleh s1. Menyalin terjadi seolah-olah n karakter dari objek yang ditunjuk oleh s2 pertama kali disalin ke array sementara karakter n yang tidak tumpang tindih objek yang ditunjukkan oleh s1 dan s2, dan kemudian karakter n dari array sementara disalin ke dalam objek yang ditunjukkan oleh s1. Kembali
3 Fungsi memmove mengembalikan nilai s1.
Satu-satunya tipe yang C ++ menjamin ukurannya char
. Dan ukurannya adalah 1. Ukuran semua jenis lainnya tergantung platform.