Apakah valid untuk menyalin struct beberapa yang anggotanya tidak diinisialisasi?
Saya menduga itu adalah perilaku yang tidak terdefinisi, tetapi jika demikian, itu membuat meninggalkan anggota yang tidak diinisialisasi dalam sebuah struct (bahkan jika anggota tersebut tidak pernah digunakan secara langsung) sangat berbahaya. Jadi saya bertanya-tanya apakah ada sesuatu dalam standar yang memungkinkannya.
Misalnya, apakah ini valid?
struct Data {
int a, b;
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
c++
initialization
copy-constructor
undefined-behavior
Tomek Czajka
sumber
sumber
Jawaban:
Ya, jika anggota yang tidak diinisialisasi bukan tipe karakter sempit yang tidak ditandatangani atau
std::byte
, kemudian menyalin struktur yang berisi nilai tak tentu ini dengan konstruktor salinan yang didefinisikan secara implisit adalah perilaku yang tidak ditentukan secara teknis, karena untuk menyalin variabel dengan nilai tak tentu dari jenis yang sama, karena dari [dcl.init] / 12 .Ini berlaku di sini, karena copy constructor yang dihasilkan secara implisit, kecuali untuk
union
s, didefinisikan untuk menyalin setiap anggota secara individual seolah-olah dengan inisialisasi langsung, lihat [class.copy.ctor] / 4 .Ini juga tunduk pada masalah CWG aktif 2264 .
Saya kira dalam praktiknya Anda tidak akan memiliki masalah dengan itu.
Jika Anda ingin 100% yakin, menggunakan
std::memcpy
selalu memiliki perilaku yang terdefinisi dengan baik jika jenisnya dapat disalin secara sepele , bahkan jika anggota memiliki nilai tak tentu.Selain masalah-masalah ini, Anda harus selalu menginisialisasi anggota kelas Anda dengan benar dengan nilai yang ditentukan pada konstruksi, dengan asumsi Anda tidak memerlukan kelas untuk memiliki konstruktor default sepele . Anda dapat melakukannya dengan mudah menggunakan sintaks penginisialisasi anggota default untuk misalnya menginisialisasi nilai anggota:
sumber
memcpy
, bahkan untuk jenis yang dapat disalin sepele. Satu-satunya pengecualian adalah serikat, di mana konstruktor copy implisit menyalin representasi objek seolah-olah olehmemcpy
.Secara umum, menyalin data yang tidak diinisialisasi adalah perilaku yang tidak terdefinisi karena data itu mungkin berada dalam kondisi terperangkap. Mengutip halaman ini :
Signalling NaN dimungkinkan untuk tipe floating point, dan pada beberapa platform bilangan bulat mungkin memiliki representasi trap.
Namun, untuk jenis yang dapat disalin sepele , dimungkinkan untuk digunakan
memcpy
untuk menyalin representasi mentah objek. Melakukannya aman karena nilai objek tidak ditafsirkan, dan sebaliknya urutan byte mentah representasi objek disalin.sumber
unsigned char[64]
)? Memperlakukan byte dari sebuah struct sebagai memiliki nilai-nilai yang tidak ditentukan dapat menghambat optimasi, tetapi membutuhkan programmer untuk secara manual mengisi array dengan nilai-nilai yang tidak berguna akan menghambat efisiensi bahkan lebih.Dalam beberapa kasus, seperti yang dijelaskan, Standar C ++ memungkinkan kompiler untuk memproses konstruksi dengan cara apa pun yang menurut pelanggan mereka paling berguna, tanpa mengharuskan perilaku dapat diprediksi. Dengan kata lain, konstruksi semacam itu memanggil "Perilaku Tidak Terdefinisi". Itu tidak berarti, bagaimanapun, bahwa konstruksi seperti itu dimaksudkan untuk "dilarang" karena Standar C ++ secara eksplisit mengesampingkan yurisdiksi atas apa yang "boleh" dilakukan oleh program yang dibentuk dengan baik. Sementara saya tidak mengetahui adanya dokumen Rationale yang diterbitkan untuk Standar C ++, fakta bahwa itu menggambarkan Perilaku Tidak Terdefinisi seperti halnya C89 akan menyarankan makna yang dimaksudkan adalah serupa: "Perilaku tidak terdefinisi memberikan lisensi implementor untuk tidak menangkap kesalahan program tertentu yang sulit. untuk mendiagnosis.
Ada banyak situasi di mana cara paling efisien untuk memproses sesuatu akan melibatkan penulisan bagian-bagian dari struktur yang akan dipedulikan kode hilir, sementara mengabaikan yang tidak dipedulikan kode hilir. Mengharuskan program menginisialisasi semua anggota struktur, termasuk yang tidak ada yang peduli, akan menghambat efisiensi.
Lebih lanjut, ada beberapa situasi di mana mungkin paling efisien untuk memiliki data yang tidak diinisialisasi berperilaku dengan cara non-deterministik. Misalnya, diberikan:
jika kode hilir tidak akan peduli dengan nilai-nilai elemen apa pun
x.dat
atauy.dat
yang indeksnya tidak tercantumarr
, kode tersebut mungkin dioptimalkan untuk:Peningkatan dalam efisiensi ini tidak akan mungkin jika programmer diminta untuk secara eksplisit menulis setiap elemen
temp.dat
, termasuk yang hilir tidak akan peduli, sebelum menyalinnya.Di sisi lain, ada beberapa aplikasi yang penting untuk menghindari kemungkinan kebocoran data. Dalam aplikasi seperti itu, mungkin berguna untuk memiliki versi kode yang diinstruksikan untuk menjebak setiap upaya untuk menyalin penyimpanan yang tidak diinisialisasi tanpa memperhatikan apakah kode hilir akan melihatnya, atau mungkin berguna untuk memiliki jaminan implementasi bahwa penyimpanan apa pun yang isinya dapat dibocorkan akan menjadi nol atau ditimpa dengan data yang tidak rahasia.
Dari apa yang dapat saya katakan, Standar C ++ tidak berusaha untuk mengatakan bahwa salah satu dari perilaku ini cukup berguna daripada yang lain untuk membenarkan mandatnya. Ironisnya, kurangnya spesifikasi ini dapat dimaksudkan untuk memfasilitasi optimasi, tetapi jika programmer tidak dapat mengeksploitasi segala jenis jaminan perilaku yang lemah, setiap optimasi akan dinegasikan.
sumber
Karena semua anggota
Data
adalah tipe primitif,data2
akan mendapatkan "salinan bit-demi-bit" yang tepat dari semua anggotadata
. Jadi nilaidata2.b
akan persis sama dengan nilaidata.b
. Namun, nilai pasti daridata.b
tidak dapat diprediksi, karena Anda belum menginisialisasi secara eksplisit. Ini akan tergantung pada nilai byte di wilayah memori yang dialokasikan untukdata
.sumber
std::memcpy
. Tak satu pun dari ini mencegah penggunaanstd::memcpy
ataustd::memmove
. Ini hanya mencegah penggunaan copy constructor implisit.