Apakah menggunakan malloc untuk int perilaku tidak terdefinisi hingga C ++ 20

96

Saya diberitahu bahwa kode berikut memiliki perilaku tidak terdefinisi hingga C ++ 20:

int *p = (int*)malloc(sizeof(int));
*p = 10;

Benarkah itu?

Argumennya adalah bahwa masa pakai intobjek tidak dimulai sebelum menetapkan nilainya ( P0593R6 ). Untuk mengatasi masalah tersebut, penempatan newharus digunakan:

int *p = (int*)malloc(sizeof(int));
new (p) int;
*p = 10;

Apakah kita benar-benar harus memanggil konstruktor default yang sepele untuk memulai masa pakai objek?

Pada saat yang sama, kode tersebut tidak memiliki perilaku tidak terdefinisi dalam C murni. Tetapi, bagaimana jika saya mengalokasikan intdalam kode C dan menggunakannya dalam kode C ++?

// C source code:
int *alloc_int(void)
{
    int *p = (int*)malloc(sizeof(int));
    *p = 10;
    return p;
}

// C++ source code:
extern "C" int *alloc_int(void);

auto p = alloc_int();
*p = 20;

Apakah ini masih perilaku yang tidak terdefinisi?

anton_rh
sumber
8
Untuk int? Tidak. Untuk std::string? Iya.
Eljay
8
@Eljay For int, juga ya. Hanya saja dalam prakteknya tidak akan menimbulkan masalah jika tidak dilakukan. Sebab std::string, hal itu jelas akan menimbulkan masalah.
Barry
Sebelum C ++ 20 Anda dapat menambahkan penempatan baru. Ini kemudian akan terbentuk dengan baik dan mungkin tidak akan memerlukan biaya apa pun.
François Andrieux
8
Apa aturan baru di C ++ 20 yang mengubah ini?
Kevin
4
Bukankah seharusnya begitu int *p = (int*)malloc(sizeof(int)); p = new(p) int;? Saya pernah menyadari bahwa tidak menetapkan hasil penempatan baru juga dapat menyebabkan efek yang fatal (meskipun mungkin terlihat agak konyol).
Scheff

Jawaban:

62

Benarkah

Iya. Secara teknis, tidak ada bagian dari:

int *p = (int*)malloc(sizeof(int));

sebenarnya membuat sebuah objek bertipe int, jadi dereferencing padalah UB karena tidak ada aktualnya di intsana.

Apakah kita benar-benar harus memanggil konstruktor default yang sepele untuk memulai waktu hidup objek?

Apakah Anda harus sesuai dengan model objek C ++ untuk menghindari perilaku tidak terdefinisi sebelum C ++ 20? Iya. Apakah ada kompiler yang benar-benar membahayakan jika Anda tidak melakukan ini? Aku tidak menyadarinya.

[...] Apakah ini masih perilaku yang tidak terdefinisi?

Iya. Sebelum C ++ 20, Anda masih belum benar-benar membuat intobjek di mana pun jadi ini adalah UB.

Barry
sumber
Komentar tidak untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Makyen
Mengapa bahasa di timsong-cpp.github.io/cppwp/n3337/basic.life#1.1 cukup untuk tidak menjadi UB? Bagaimanapun, penyimpanan dengan ukuran dan kesejajaran yang tepat diperoleh untuk intcontoh - masa pakai intobjek dimulai di sana.
avakar
41

Ya, UB. Daftar cara sebuah intbisa ada disebutkan, dan tidak ada yang berlaku di sana, kecuali Anda berpendapat bahwa malloc itu acausal.

Ini secara luas dianggap sebagai kekurangan dalam standar, tetapi salah satu yang kurang penting, karena optimasi yang dilakukan oleh kompiler C ++ di sekitar bit tertentu UB tidak menimbulkan masalah dengan use case tersebut.

Adapun pertanyaan ke-2, C ++ tidak mengamanatkan bagaimana C ++ dan C berinteraksi. Jadi semua interaksi dengan C adalah ... UB, alias perilaku tidak ditentukan oleh standar C ++.

Yakk - Adam Nevraumont
sumber
5
Bisakah Anda memperluas daftar cara yang disebutkan agar int ada? Saya ingat menanyakan pertanyaan serupa tentang umur tipe primitif, dan diberitahu bahwa primitif bisa "ada" hanya dengan mengatakan itu ada karena spesifikasi tidak mengatakan sebaliknya. Sepertinya saya melewatkan bagian yang berguna dari spesifikasi! Saya ingin tahu bagian mana yang harus saya teliti!
Cort Ammon
7
@CortAmmon Daftar cara yang disebutkan untuk objek (jenis apa pun) agar ada di C ++ 20 ada di [intro.object] : (1) menurut definisi (2) oleh ekspresi baru (3) secara implisit sesuai aturan baru di P0593 (4) mengubah anggota aktif serikat (5) sementara. (3) baru di C ++ 20, (4) baru di C ++ 17.
Barry
3
Apakah interaksi C / C ++ benar-benar UB? Akan lebih masuk akal untuk menetapkan implementasi, daripada tidak terdefinisi, jika tidak, akan aneh bahkan memiliki extern "C"sintaks sama sekali.
Ruslan
4
@Ruslan: Implementasi bebas untuk mendefinisikan perilaku apa pun yang tidak ditentukan oleh ISO C ++. (Misalnya gcc -fno-strict-aliasing, atau MSVC secara default). Mengatakan "implementasi yang ditentukan" akan membutuhkan semua implementasi C ++ untuk menentukan beberapa cara di mana mereka beroperasi dengan beberapa implementasi C, jadi masuk akal untuk menyerahkan sepenuhnya pada implementasi apakah mereka ingin melakukan hal seperti itu atau tidak.
Peter Cordes
4
@PeterCordes: Saya bertanya-tanya mengapa begitu banyak orang gagal untuk mengenali perbedaan antara IDB dan UB, dan mengadopsi beberapa gagasan yang fantastis bahwa kegagalan Standar untuk mengamanatkan bahwa semua implementasi memproses sebuah konstruksi secara bermakna menyiratkan penilaian bahwa tidak ada implementasi yang diharapkan untuk melakukannya, dan implementasi yang tidak melakukannya tidak boleh dianggap inferior.
supercat