Apakah variabel lokal tidak diinisialisasi sebagai penghasil angka acak tercepat?

329

Saya tahu variabel lokal yang tidak diinisialisasi adalah perilaku tidak terdefinisi ( UB ), dan juga nilainya mungkin memiliki representasi jebakan yang dapat mempengaruhi operasi lebih lanjut, tetapi kadang-kadang saya ingin menggunakan nomor acak hanya untuk representasi visual dan tidak akan menggunakannya lebih lanjut di bagian lain dari program, misalnya, mengatur sesuatu dengan warna acak dalam efek visual, misalnya:

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

apakah itu lebih cepat dari

void updateEffect(){
    for(int i=0;i<1000;i++){
        star[i].setColor(rand()%255,rand()%255,rand()%255);
        star[i].setVisible(rand()%2==0?true:false);
    }
}

dan juga lebih cepat dari generator nomor acak lainnya?

ggrr
sumber
88
+1 Ini adalah pertanyaan yang sangat sah. Memang benar bahwa dalam praktiknya, nilai yang tidak diinisialisasi mungkin agak acak. Fakta bahwa mereka tidak terlalu khusus dan itu adalah UB tidak membuat meminta seburuk itu.
imallett
35
@ imallett: Tentu saja. Ini adalah pertanyaan yang bagus, dan setidaknya satu game Z80 (Amstrad / ZX Spectrum) lama di masa lalu menggunakan programnya sebagai data untuk mengatur medannya. Jadi bahkan ada preseden. Tidak bisa melakukannya hari ini. Sistem operasi modern menghilangkan semua kesenangan.
Batsyeba
81
Tentunya masalah utamanya adalah tidak acak.
john
30
Bahkan, ada contoh variabel tidak diinisialisasi yang digunakan sebagai nilai acak, lihat bencana Debian RNG (Contoh 4 dalam artikel ini ).
PaperBirdMaster
31
Dalam prakteknya - dan percayalah, saya melakukan banyak debug pada berbagai arsitektur - solusi Anda dapat melakukan dua hal: membaca register yang tidak diinisialisasi atau memori yang tidak diinisialisasi. Sekarang sementara "tidak diinisialisasi" berarti acak dengan cara tertentu, dalam praktiknya kemungkinan besar akan berisi a) nol , b) pengulangan atau nilai yang konsisten (dalam hal membaca memori yang sebelumnya ditempati oleh media digital) atau c) sampah yang konsisten dengan nilai terbatas set (dalam hal membaca memori yang sebelumnya ditempati oleh data digital yang disandikan). Tak satu pun dari mereka adalah sumber entropi nyata.
mg30rg

Jawaban:

299

Seperti yang telah dicatat oleh orang lain, ini adalah Perilaku Tidak Terdefinisi (UB).

Dalam praktiknya, itu akan (mungkin) benar-benar (jenis) bekerja. Membaca dari register yang tidak diinisialisasi pada arsitektur x86 [-64] memang akan menghasilkan hasil sampah, dan mungkin tidak akan melakukan hal buruk (sebagai lawan dari mis Itanium, di mana register dapat ditandai sebagai tidak valid , sehingga membaca kesalahan propagasi seperti NaN).

Ada dua masalah utama:

  1. Ini tidak akan terlalu acak. Dalam hal ini, Anda membaca dari tumpukan, sehingga Anda akan mendapatkan apa pun yang ada di sana sebelumnya. Yang mungkin acak, terstruktur sepenuhnya, kata sandi yang Anda masukkan sepuluh menit yang lalu, atau resep kue nenek Anda.

  2. Adalah praktik buruk (huruf kapital 'B') untuk membiarkan hal-hal seperti ini merayapi kode Anda. Secara teknis, kompiler dapat memasukkan reformat_hdd();setiap kali Anda membaca variabel yang tidak ditentukan. Tidak akan , tetapi Anda tidak harus tetap melakukannya. Jangan lakukan hal-hal yang tidak aman. Semakin sedikit pengecualian yang Anda buat, semakin aman Anda dari kesalahan yang tidak disengaja sepanjang waktu.

Masalah yang lebih mendesak dengan UB adalah bahwa hal itu membuat perilaku seluruh program Anda tidak terdefinisi. Kompiler modern dapat menggunakan ini untuk menghilangkan sebagian besar kode Anda atau bahkan kembali ke masa lalu . Bermain dengan UB seperti insinyur Victoria yang membongkar reaktor nuklir langsung. Ada banyak hal yang salah, dan Anda mungkin tidak akan tahu setengah dari prinsip dasar atau teknologi yang diimplementasikan. Ini mungkin baik-baik saja, tapi Anda masih tidak boleh membiarkan hal itu terjadi. Lihatlah jawaban bagus lainnya untuk perincian.

Juga, saya akan memecat Anda.

Imallett
sumber
39
@Potatoswatter: Register Itanium dapat berisi NaT (Bukan Hal) yang berlaku adalah "register tidak diinisialisasi". Pada Itanium, membaca dari sebuah register ketika Anda belum menulisnya dapat membatalkan program Anda (baca lebih lanjut di sini: blogs.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx ). Jadi ada alasan bagus mengapa membaca nilai yang tidak diinisialisasi adalah perilaku yang tidak terdefinisi. Itu juga mungkin salah satu alasan mengapa Itanium tidak terlalu populer :)
keras
58
Saya benar-benar keberatan dengan gagasan "itu semacam bekerja". Bahkan jika itu benar hari ini, yang tidak, itu mungkin berubah kapan saja karena penyusun yang lebih agresif. Kompiler dapat mengganti bacaan apa pun dengan unreachable()dan menghapus setengah dari program Anda. Ini juga terjadi dalam praktik. Perilaku ini benar-benar menetralkan RNG di beberapa distro Linux yang saya percaya .; Sebagian besar jawaban dalam pertanyaan ini tampaknya menganggap bahwa nilai yang tidak diinisialisasi berlaku seperti nilai sama sekali. Itu salah.
usr
25
Juga, saya akan memecat Anda sepertinya hal yang konyol untuk mengatakan, dengan asumsi praktik yang baik ini harus ditangkap pada tinjauan kode, dibahas dan tidak boleh terjadi lagi. Ini pasti harus ditangkap karena kita menggunakan bendera peringatan yang benar, kan?
Shafik Yaghmour
17
@Michael Sebenarnya, benar. Jika suatu program memiliki perilaku tidak terdefinisi pada titik mana pun, kompilator dapat mengoptimalkan program Anda dengan cara yang memengaruhi kode sebelumnya yang menerapkan perilaku tidak terdefinisi. Ada berbagai artikel dan demonstrasi tentang bagaimana membingungkan ini bisa dapatkan. Berikut ini cukup bagus: blogs.msdn.com/b/oldnewthing/archive/2014/06/27/10537746.aspx (yang mencakup bit dalam standar yang mengatakan semua taruhan dibatalkan jika ada jalur dalam program Anda yang memanggil UB)
Tom Tanner
19
Jawaban ini membuatnya terdengar seolah-olah "menerapkan perilaku tidak terdefinisi secara teori buruk, tetapi itu tidak akan benar-benar melukai Anda dalam praktik" . Itu salah. Mengumpulkan entropi dari ekspresi yang akan menyebabkan UB dapat (dan mungkin akan ) menyebabkan semua entropi yang dikumpulkan sebelumnya menjadi hilang . Ini bahaya serius.
Theodoros Chatzigiannakis
213

Izinkan saya mengatakan ini dengan jelas: kami tidak meminta perilaku yang tidak ditentukan dalam program kami . Itu tidak pernah merupakan ide yang bagus, titik. Ada pengecualian langka untuk aturan ini; misalnya, jika Anda adalah pelaksana perpustakaan yang menerapkan offsetof . Jika kasing Anda berada dalam pengecualian seperti itu, Anda mungkin sudah tahu ini. Dalam hal ini kita tahu menggunakan variabel otomatis tidak diinisialisasi adalah perilaku yang tidak terdefinisi .

Kompiler menjadi sangat agresif dengan optimisasi di sekitar perilaku yang tidak terdefinisi dan kami dapat menemukan banyak kasus di mana perilaku yang tidak terdefinisi telah menyebabkan kelemahan keamanan. Kasus yang paling terkenal mungkin adalah penghapusan cek pointer nol kernel Linux yang saya sebutkan dalam jawaban saya untuk bug kompilasi C ++? di mana optimisasi kompiler di sekitar perilaku yang tidak ditentukan mengubah perulangan menjadi yang tak terbatas.

Kita dapat membaca Optimalisasi CERT yang Berbahaya dan Kehilangan Kausalitas ( video ) yang mengatakan, antara lain:

Semakin, penulis kompiler mengambil keuntungan dari perilaku yang tidak terdefinisi dalam bahasa pemrograman C dan C ++ untuk meningkatkan optimisasi.

Seringkali, optimasi ini mengganggu kemampuan pengembang untuk melakukan analisis sebab-akibat pada kode sumber mereka, yaitu, menganalisis ketergantungan hasil hilir pada hasil sebelumnya.

Akibatnya, optimasi ini menghilangkan kausalitas dalam perangkat lunak dan meningkatkan kemungkinan kesalahan, cacat, dan kerentanan perangkat lunak.

Khususnya berkenaan dengan nilai-nilai tak tentu, laporan cacat standar C 451: Ketidakstabilan variabel otomatis tidak diinisialisasi membuat beberapa bacaan yang menarik. Ini belum diselesaikan tetapi memperkenalkan konsep nilai goyah yang berarti ketidakpastian suatu nilai dapat menyebar melalui program dan dapat memiliki nilai tak tentu yang berbeda di berbagai titik dalam program.

Saya tidak tahu ada contoh di mana ini terjadi tetapi pada titik ini kita tidak bisa mengesampingkannya.

Contoh nyata, bukan hasil yang Anda harapkan

Anda tidak mungkin mendapatkan nilai acak. Sebuah kompiler dapat mengoptimalkan loop jauh sama sekali. Misalnya, dengan case sederhana ini:

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r ;
    }
}

dentang mengoptimalkannya ( lihat langsung ):

updateEffect(int*):                     # @updateEffect(int*)
    retq

atau mungkin dapatkan semua nol, seperti halnya case yang dimodifikasi ini:

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r%255 ;
    }
}

lihat langsung :

updateEffect(int*):                     # @updateEffect(int*)
    xorps   %xmm0, %xmm0
    movups  %xmm0, 64(%rdi)
    movups  %xmm0, 48(%rdi)
    movups  %xmm0, 32(%rdi)
    movups  %xmm0, 16(%rdi)
    movups  %xmm0, (%rdi)
    retq

Kedua kasus ini adalah bentuk perilaku yang tidak dapat diterima secara sempurna.

Catatan, jika kita menggunakan Itanium kita bisa berakhir dengan nilai jebakan :

[...] jika register memiliki nilai bukan-hal-khusus, bacalah jebakan register kecuali untuk beberapa instruksi [...]

Catatan penting lainnya

Sangat menarik untuk mencatat perbedaan antara gcc dan dentang yang dicatat dalam proyek Canaries UB tentang seberapa bersedia mereka untuk mengambil keuntungan dari perilaku yang tidak terdefinisi sehubungan dengan memori yang tidak diinisialisasi. Catatan artikel ( penekanan saya ):

Tentu saja kita harus benar-benar jelas dengan diri kita sendiri bahwa setiap harapan seperti itu tidak ada hubungannya dengan standar bahasa dan segala sesuatu yang berkaitan dengan apa yang dilakukan oleh kompiler tertentu, baik karena penyedia kompiler itu tidak mau mengeksploitasi UB itu atau hanya karena mereka belum sempat mengeksploitasinya . Ketika tidak ada jaminan nyata dari penyedia kompiler, kami ingin mengatakan bahwa UB yang belum dieksploitasi adalah bom waktu : mereka menunggu untuk meledak bulan depan atau tahun depan ketika kompiler menjadi sedikit lebih agresif.

Seperti yang ditunjukkan oleh Matthieu M., Apa yang Harus Diketahui Setiap Pemrogram C Tentang Perilaku Tidak Terdefinisi # 2/3 juga relevan dengan pertanyaan ini. Itu mengatakan antara lain ( penekanan milikku ):

Yang penting dan menakutkan untuk disadari adalah bahwa hampir semua optimasi berdasarkan perilaku yang tidak terdefinisi dapat mulai dipicu pada kode kereta kapan saja di masa depan . Inlining, loop membuka gulungan, promosi memori dan optimasi lainnya akan terus menjadi lebih baik, dan sebagian besar alasan mereka ada adalah untuk mengekspos optimasi sekunder seperti yang di atas.

Bagi saya, ini sangat tidak memuaskan, sebagian karena kompiler akhirnya disalahkan, tetapi juga karena itu berarti bahwa sejumlah besar kode C adalah ranjau darat yang sedang menunggu untuk meledak.

Demi kelengkapan saya mungkin harus menyebutkan bahwa implementasi dapat memilih untuk membuat perilaku yang tidak terdefinisi dengan baik, misalnya gcc memungkinkan jenis hukuman melalui serikat pekerja sementara di C ++ ini tampak seperti perilaku yang tidak terdefinisi . Jika ini masalahnya, implementasi harus mendokumentasikannya dan ini biasanya tidak portabel.

Shafik Yaghmour
sumber
1
+ (int) (PI / 3) untuk contoh keluaran kompiler; contoh nyata bahwa UB adalah, yah, UB .
2
Memanfaatkan UB secara efektif digunakan untuk menjadi merek dagang dari peretas yang hebat. Tradisi ini telah berlangsung selama mungkin 50 tahun atau lebih sekarang. Sayangnya, komputer sekarang diperlukan untuk meminimalkan efek UB karena orang jahat. Saya benar-benar menikmati mencari tahu bagaimana melakukan hal-hal keren dengan kode mesin UB atau port baca / tulis, dll saya tahun 90-an, ketika OS tidak mampu melindungi pengguna dari diri mereka sendiri.
sfdcfox
1
@ sfdcfox jika Anda melakukannya dalam kode mesin / assembler, itu bukan perilaku yang tidak terdefinisi (itu mungkin perilaku yang tidak konvensional).
Caleth
2
Jika Anda memiliki kumpulan spesifik dalam pikiran, maka gunakan itu dan jangan menulis C. tidak patuh. Kemudian semua orang akan tahu Anda menggunakan trik non-portabel tertentu. Dan itu bukan Orang Jahat yang berarti Anda tidak dapat menggunakan UB, itu Intel dll melakukan trik mereka pada chip.
Caleth
2
@ 500-InternalServerError karena mereka mungkin tidak mudah terdeteksi atau mungkin tidak terdeteksi sama sekali dalam kasus umum dan karenanya tidak akan ada cara untuk melarang mereka. Yang berbeda maka pelanggaran tata bahasa yang bisa dideteksi. Kami juga tidak memiliki diagnosa yang buruk dan buruk yang secara umum memisahkan program yang buruk yang dapat dideteksi secara teori dari yang secara teori tidak dapat dideteksi dengan andal.
Shafik Yaghmour
164

Tidak, ini mengerikan.

Perilaku menggunakan variabel yang tidak diinisialisasi tidak ditentukan dalam C dan C ++, dan sangat tidak mungkin bahwa skema seperti itu akan memiliki sifat statistik yang diinginkan.

Jika Anda menginginkan generator nomor acak "cepat dan kotor", maka rand()itulah taruhan terbaik Anda. Dalam implementasinya, yang dilakukannya hanyalah multiplikasi, tambahan, dan modulus.

Generator tercepat yang saya tahu mengharuskan Anda untuk menggunakan uint32_tsebagai jenis variabel pseudo-acak I, dan menggunakan

I = 1664525 * I + 1013904223

untuk menghasilkan nilai-nilai berturut-turut. Anda dapat memilih nilai awal apa pun I(disebut benih ) yang disukai Anda. Jelas Anda dapat membuat kode sebaris itu. Sampul yang dijamin standar dari tipe yang tidak ditandatangani bertindak sebagai modulus. (Konstanta numerik dipilih langsung oleh programmer ilmiah yang luar biasa itu, Donald Knuth.)

Batsyeba
sumber
9
Generator "linear congruential" yang Anda tampilkan bagus untuk aplikasi sederhana, tetapi hanya untuk aplikasi non-kriptografi. Dimungkinkan untuk memprediksi perilakunya. Lihat misalnya " Menguraikan enkripsi kongruensi linier " oleh Don Knuth sendiri (Transaksi IEEE tentang Teori Informasi, Volume 31)
Jay
24
@ Mungkin dibandingkan dengan variabel unit untuk cepat dan kotor? Ini adalah solusi yang jauh lebih baik.
Mike McMahon
2
rand()tidak cocok untuk tujuan dan harus sepenuhnya ditinggalkan, menurut pendapat saya. Saat ini Anda dapat mengunduh generator nomor acak yang berlisensi dan jauh lebih unggul (mis. Mersenne Twister) yang sangat cepat dengan kemudahan terbesar sehingga benar-benar tidak perlu terus menggunakan yang sangat cacatrand()
Jack Aidley
1
rand () memiliki masalah mengerikan lainnya: ia menggunakan semacam kunci, disebut di dalam utas, memperlambat kode Anda secara dramatis. Setidaknya, ada versi reentrant. Dan jika Anda menggunakan C ++ 11, API acak menyediakan semua yang Anda butuhkan.
Marwan Burelle
4
Agar adil, dia tidak bertanya apakah itu generator angka acak yang baik. Dia bertanya apakah itu cepat. Ya, mungkin itu puasa., Tapi hasilnya tidak akan acak sama sekali.
jcoder
42

Pertanyaan bagus!

Tidak terdefinisi tidak berarti itu acak. Pikirkan tentang hal ini, nilai-nilai yang Anda dapatkan dalam variabel global yang tidak diinisialisasi ditinggalkan di sana oleh sistem atau aplikasi Anda / lainnya yang sedang berjalan. Bergantung apa yang sistem Anda lakukan dengan memori yang tidak lagi digunakan dan / atau nilai-nilai apa yang dihasilkan sistem dan aplikasi, Anda mungkin mendapatkan:

  1. Selalu sama.
  2. Jadilah salah satu dari sekumpulan nilai kecil.
  3. Dapatkan nilai dalam satu atau beberapa rentang kecil.
  4. Lihat banyak nilai yang dapat dibagi 2/4/8 dari pointer pada sistem 16/32/64-bit
  5. ...

Nilai yang akan Anda dapatkan sepenuhnya bergantung pada nilai non-acak yang ditinggalkan oleh sistem dan / atau aplikasi. Jadi, memang akan ada beberapa kebisingan (kecuali sistem Anda menghapus memori yang tidak digunakan lagi), tetapi kumpulan nilai dari mana Anda akan menggambar tidak akan acak.

Hal-hal menjadi jauh lebih buruk untuk variabel lokal karena ini datang langsung dari tumpukan program Anda sendiri. Ada peluang yang sangat baik bahwa program Anda akan benar-benar menulis lokasi tumpukan ini selama eksekusi kode lainnya. Saya memperkirakan peluang keberuntungan dalam situasi ini sangat rendah, dan perubahan kode 'acak' yang Anda buat mencoba keberuntungan ini.

Baca tentang keacakan . Seperti yang akan Anda lihat, keacakan adalah properti yang sangat spesifik dan sulit diperoleh. Adalah kesalahan umum untuk berpikir bahwa jika Anda hanya mengambil sesuatu yang sulit dilacak (seperti saran Anda) Anda akan mendapatkan nilai acak.

makna-hal
sumber
7
... dan itu meninggalkan semua optimisasi kompiler yang akan sepenuhnya mengeluarkan kode itu.
Deduplicator
6 ... Anda akan mendapatkan "keacakan" yang berbeda di Debug dan Rilis. Tidak terdefinisi berarti Anda melakukan kesalahan.
Sql Surfer
Baik. Saya akan menyingkat atau merangkum dengan "undefined"! = "Arbitrary"! = "Random". Semua jenis "ketidaktahuan" ini memiliki sifat yang berbeda.
fche
Variabel global dijamin memiliki nilai yang ditentukan, apakah diinisialisasi secara eksplisit atau tidak. Ini jelas benar di C ++ dan di C juga .
Brian Vandenberg
32

Banyak jawaban bagus, tetapi izinkan saya untuk menambahkan yang lain dan tekankan bahwa di komputer deterministik, tidak ada yang acak. Ini berlaku untuk angka yang dihasilkan oleh pseudo-RNG dan angka yang "acak" yang ditemukan di area memori yang disediakan untuk variabel lokal C / C ++ pada stack.

TAPI ... ada perbedaan penting.

Angka-angka yang dihasilkan oleh generator pseudorandom yang baik memiliki properti yang membuatnya secara statistik mirip dengan undian yang benar-benar acak. Misalnya, distribusinya seragam. Panjang siklus panjang: Anda bisa mendapatkan jutaan angka acak sebelum siklus berulang. Urutannya tidak berkorelasi otomatis: misalnya, Anda tidak akan mulai melihat pola aneh muncul jika Anda mengambil setiap angka ke-2, ke-3, atau ke-27, atau jika Anda melihat angka-angka tertentu dalam angka yang dihasilkan.

Sebaliknya, angka "acak" yang tertinggal di tumpukan tidak memiliki properti ini. Nilai-nilai mereka dan keacakan nyata mereka bergantung sepenuhnya pada bagaimana program itu dibangun, bagaimana itu dikompilasi, dan bagaimana itu dioptimalkan oleh kompiler. Sebagai contoh, berikut adalah variasi ide Anda sebagai program mandiri:

#include <stdio.h>

notrandom()
{
        int r, g, b;

        printf("R=%d, G=%d, B=%d", r&255, g&255, b&255);
}

int main(int argc, char *argv[])
{
        int i;
        for (i = 0; i < 10; i++)
        {
                notrandom();
                printf("\n");
        }

        return 0;
}

Ketika saya mengkompilasi kode ini dengan GCC pada mesin Linux dan menjalankannya, ternyata menjadi deterministik yang agak tidak menyenangkan:

R=0, G=19, B=0
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255

Jika Anda melihat kode yang dikompilasi dengan disassembler, Anda dapat merekonstruksi apa yang sedang terjadi, secara detail. Panggilan pertama ke notrandom () menggunakan area stack yang sebelumnya tidak digunakan oleh program ini; siapa tahu apa yang ada di sana. Tapi setelah panggilan itu ke notrandom (), ada panggilan ke printf () (yang sebenarnya dioptimalkan oleh kompiler GCC untuk panggilan ke putchar (), tapi tidak apa-apa) dan yang menimpa tumpukan. Jadi waktu berikutnya dan berikutnya, ketika notrandom () dipanggil, stack akan berisi data basi dari eksekusi putchar (), dan karena putchar () selalu dipanggil dengan argumen yang sama, data basi ini akan selalu sama, terlalu.

Jadi sama sekali tidak ada yang acak tentang perilaku ini, juga angka-angka yang diperoleh dengan cara ini tidak memiliki sifat yang diinginkan dari generator nomor pseudorandom yang ditulis dengan baik. Bahkan, dalam sebagian besar skenario kehidupan nyata, nilai-nilai mereka akan berulang dan sangat berkorelasi.

Memang, seperti yang lain, saya juga akan secara serius mempertimbangkan memecat seseorang yang mencoba untuk menularkan ide ini sebagai "RNG kinerja tinggi".

Viktor Toth
sumber
1
“Di komputer deterministik, tidak ada yang acak” - Ini sebenarnya tidak benar. Komputer modern mengandung semua jenis sensor yang memungkinkan Anda menghasilkan keacakan yang benar dan tidak dapat diprediksi tanpa generator perangkat keras terpisah. Pada arsitektur modern, nilai-nilai /dev/randomsering diunggulkan dari sumber perangkat keras seperti itu, dan pada kenyataannya "noise kuantum", yaitu benar-benar tidak dapat diprediksi dalam arti fisik terbaik dari kata tersebut.
Konrad Rudolph
2
Tapi kemudian, itu bukan komputer deterministik, bukan? Anda sekarang mengandalkan input lingkungan. Bagaimanapun, ini membawa kita jauh melampaui diskusi tentang pseudo-RNG konvensional vs. bit "acak" dalam memori yang tidak diinisialisasi. Juga ... lihat deskripsi / dev / random untuk menghargai seberapa jauh dari cara mereka pelaksana pergi untuk memastikan bahwa angka-angka acak aman secara kriptografis ... tepatnya karena sumber input tidak murni, kebisingan kuantum tidak berkorelasi tetapi lebih tepatnya, pembacaan sensor yang berpotensi sangat berkorelasi dengan hanya tingkat keacakan yang kecil. Cukup lambat juga.
Viktor Toth
29

Perilaku tidak terdefinisi berarti bahwa pembuat kompiler bebas untuk mengabaikan masalah karena programmer tidak akan pernah memiliki hak untuk mengeluh apa pun yang terjadi.

Sementara secara teori ketika memasuki tanah UB apa pun bisa terjadi (termasuk daemon yang terbang keluar dari hidung Anda ) yang biasanya berarti bahwa penulis kompiler tidak akan peduli dan, untuk variabel lokal, nilainya akan menjadi apa pun yang ada di memori tumpukan pada saat itu .

Ini juga berarti bahwa seringkali isinya akan "aneh" tetapi tetap atau sedikit acak atau variabel tetapi dengan pola yang jelas (misalnya meningkatkan nilai pada setiap iterasi).

Pasti Anda tidak bisa berharap itu menjadi generator acak yang layak.

6502
sumber
28

Perilaku tidak terdefinisi tidak terdefinisi. Itu tidak berarti bahwa Anda mendapatkan nilai yang tidak ditentukan, itu berarti bahwa program dapat melakukan apa saja dan masih memenuhi spesifikasi bahasa.

Compiler pengoptimalan yang baik harus diambil

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

dan kompilasi ke noop. Ini tentu saja lebih cepat daripada alternatif apa pun. Ini memiliki kelemahan yang tidak akan melakukan apa-apa, tetapi itu adalah kelemahan dari perilaku yang tidak terdefinisi.

Martijn
sumber
3
Banyak tergantung pada apakah tujuan dari kompiler adalah membantu programmer menghasilkan file yang dapat dieksekusi yang memenuhi persyaratan domain, atau apakah tujuannya adalah untuk menghasilkan executable yang paling "efisien" yang perilakunya akan konsisten dengan persyaratan minimal dari Standar C, tanpa mempertimbangkan apakah perilaku tersebut akan melayani tujuan yang bermanfaat. Berkenaan dengan tujuan sebelumnya, memiliki kode menggunakan beberapa nilai awal arbitrer untuk r, g, b, atau memicu jebakan debugger jika praktis, akan lebih berguna daripada mengubah kode menjadi nop. Berkenaan dengan tujuan yang terakhir ...
supercat
2
... kompiler yang optimal harus menentukan input apa yang akan menyebabkan metode di atas dieksekusi, dan menghilangkan kode apa pun yang hanya akan relevan ketika input tersebut diterima.
supercat
1
@supercat Atau tujuannya bisa C. untuk menghasilkan executable yang efisien sesuai dengan Standar sambil membantu programmer menemukan tempat di mana kepatuhan mungkin tidak berguna. Kompiler dapat memenuhi tujuan kompromi ini dengan mengeluarkan lebih banyak diagnostik dari yang dibutuhkan oleh Standar, seperti yang dimiliki GCC -Wall -Wextra.
Damian Yerrick
1
Bahwa nilai tidak terdefinisi tidak berarti bahwa perilaku kode di sekitarnya tidak terdefinisi. Tidak ada kompiler yang seharusnya memiliki fungsi tersebut. Dua panggilan fungsi, input apa pun yang diberikan, benar-benar HARUS disebut; yang pertama HARUS dipanggil dengan tiga angka antara 0 dan 255, dan yang kedua HARUS dipanggil dengan nilai benar atau salah. "Kompilator pengoptimal yang baik" dapat mengoptimalkan params fungsi ke nilai statis sewenang-wenang, menyingkirkan variabel sepenuhnya, tapi itu sejauh yang bisa dilakukan (well, kecuali fungsi itu sendiri dapat direduksi menjadi noops pada input tertentu).
Dewi Morgan
@DewiMorgan - karena fungsi yang dipanggil adalah dari tipe "set parameter ini", mereka hampir pasti mengurangi ke noops ketika input sama dengan nilai saat ini dari parameter, yang oleh kompiler bebas untuk mengasumsikan adalah kasusnya.
Jules
18

Belum disebutkan, tetapi jalur kode yang menjalankan perilaku tidak terdefinisi diizinkan untuk melakukan apa pun yang diinginkan kompilator, mis

void updateEffect(){}

Yang pasti lebih cepat dari loop yang benar, dan karena UB, sangat sesuai.

Caleth
sumber
18

Karena alasan keamanan, memori baru yang ditetapkan untuk suatu program harus dibersihkan, jika tidak informasi tersebut dapat digunakan, dan kata sandi dapat bocor dari satu aplikasi ke aplikasi lainnya. Hanya ketika Anda menggunakan kembali memori, Anda mendapatkan nilai yang berbeda dari 0. Dan sangat mungkin, bahwa pada stack nilai sebelumnya baru saja diperbaiki, karena penggunaan memori sebelumnya sudah diperbaiki.

Arne
sumber
13

Contoh kode khusus Anda mungkin tidak akan melakukan apa yang Anda harapkan. Sementara secara teknis setiap iterasi dari loop menciptakan kembali variabel lokal untuk nilai r, g, dan b, dalam praktiknya itu adalah ruang memori yang sama persis pada stack. Oleh karena itu ia tidak akan diacak ulang dengan setiap iterasi, dan Anda akhirnya akan menetapkan 3 nilai yang sama untuk masing-masing 1000 warna, terlepas dari seberapa acak r, g, dan b secara individual dan awalnya.

Memang, jika itu berhasil, saya akan sangat ingin tahu apa yang mengacak ulang itu. Satu-satunya hal yang dapat saya pikirkan adalah interleave interrupt yang dibajak di atas tumpukan itu, sangat tidak mungkin. Mungkin optimasi internal yang menjadikannya sebagai variabel register daripada sebagai lokasi memori yang sebenarnya, di mana register digunakan kembali lebih jauh dalam loop, akan melakukan trik, juga, terutama jika fungsi visibilitas yang diatur terutama register-hung. Namun, masih jauh dari acak.

Jos
sumber
12

Karena sebagian besar orang di sini menyebutkan perilaku yang tidak terdefinisi. Tidak terdefinisi juga berarti bahwa Anda mungkin mendapatkan beberapa nilai integer yang valid (untungnya) dan dalam hal ini akan lebih cepat (karena panggilan fungsi rand tidak dilakukan). Tapi jangan praktis menggunakannya. Saya yakin ini akan hasil yang mengerikan karena keberuntungan tidak bersama Anda sepanjang waktu.

Ali Kazmi
sumber
1
Poin yang sangat bagus! Ini mungkin trik yang pragmatis, tetapi memang itu yang membutuhkan keberuntungan.
maknanya penting
1
Sama sekali tidak ada keberuntungan yang terlibat. Jika kompiler tidak mengoptimalkan perilaku yang tidak terdefinisi, nilai yang Anda dapatkan akan menjadi deterministik sempurna (= bergantung sepenuhnya pada program Anda, inputnya, kompilernya, pustaka yang digunakannya, waktu utas-utasnya jika memiliki utas). Masalahnya adalah Anda tidak dapat menalar tentang nilai-nilai ini karena mereka bergantung pada detail implementasi.
cmaster - mengembalikan monica
Dengan tidak adanya sistem operasi dengan tumpukan penanganan interupsi yang terpisah dari tumpukan aplikasi, keberuntungan mungkin terlibat, karena gangguan sering mengganggu isi memori sedikit di luar isi tumpukan saat ini.
supercat
12

Sangat buruk! Kebiasaan buruk, hasil buruk. Mempertimbangkan:

A_Function_that_use_a_lot_the_Stack();
updateEffect();

Jika fungsinya A_Function_that_use_a_lot_the_Stack()membuat inisialisasi yang selalu sama, ia meninggalkan tumpukan dengan data yang sama. Data itulah yang kami panggil updateEffect(): selalu bernilai sama! .

Frankie_C
sumber
11

Saya melakukan tes yang sangat sederhana, dan itu tidak acak sama sekali.

#include <stdio.h>

int main() {

    int a;
    printf("%d\n", a);
    return 0;
}

Setiap kali saya menjalankan program, ia mencetak nomor yang sama ( 32767dalam kasus saya) - Anda tidak bisa mendapatkan jauh lebih sedikit dari itu. Ini mungkin apa pun kode startup di pustaka runtime yang tersisa di stack. Karena ia menggunakan kode startup yang sama setiap kali program berjalan, dan tidak ada yang berbeda dalam program antar kali, hasilnya sangat konsisten.

Barmar
sumber
Poin yang bagus. Hasil sangat tergantung pada di mana generator nomor "acak" ini disebut dalam kode. Ini agak tidak dapat diprediksi daripada acak.
NO_NAME
10

Anda harus memiliki definisi tentang apa yang Anda maksud dengan 'acak'. Definisi yang masuk akal melibatkan bahwa nilai-nilai yang Anda dapatkan harus memiliki sedikit korelasi. Itu sesuatu yang bisa Anda ukur. Ini juga tidak mudah untuk dicapai dengan cara yang terkontrol dan dapat direproduksi. Jadi perilaku yang tidak terdefinisi tentu bukan yang Anda cari.

Zsolt Szatmari
sumber
7

Ada situasi tertentu di mana memori yang tidak diinisialisasi dapat dibaca dengan aman menggunakan tipe "unsigned char *" [misalnya buffer yang dikembalikan dari malloc]. Kode dapat membaca memori seperti itu tanpa harus khawatir tentang kompiler yang membuang kausalitas ke luar jendela, dan ada kalanya mungkin lebih efisien untuk menyiapkan kode untuk apa pun yang mungkin dikandung memori daripada memastikan bahwa data yang tidak diinisialisasi tidak akan dibaca ( contoh umum dari ini akan digunakan memcpypada buffer yang diinisialisasi sebagian daripada menyalin semua elemen yang berisi data yang bermakna).

Bahkan dalam kasus seperti itu, bagaimanapun, kita harus selalu berasumsi bahwa jika kombinasi byte akan sangat menjengkelkan, membacanya akan selalu menghasilkan pola byte (dan jika pola tertentu akan menjadi vexatious dalam produksi, tetapi tidak dalam pengembangan, seperti pola tidak akan muncul sampai kode diproduksi).

Membaca memori yang tidak diinisialisasi mungkin berguna sebagai bagian dari strategi generasi acak dalam sistem tertanam di mana orang dapat yakin memori tidak pernah ditulis dengan konten yang secara substansial non-acak sejak terakhir kali sistem dinyalakan, dan jika manufaktur proses yang digunakan untuk memori menyebabkan status power-on bervariasi secara semi-acak. Kode harus berfungsi bahkan jika semua perangkat selalu menghasilkan data yang sama, tetapi dalam kasus di mana misalnya sekelompok node masing-masing harus memilih ID unik sembarang mungkin, memiliki generator "tidak terlalu acak" yang memberikan setengah node awal yang sama ID mungkin lebih baik daripada tidak memiliki sumber asal keacakan sama sekali.

supercat
sumber
2
"jika kombinasi byte akan sangat menjengkelkan, membacanya akan selalu menghasilkan pola byte" - sampai Anda kode untuk mengatasi pola itu, di mana saat itu tidak lagi menjengkelkan dan pola yang berbeda akan dibaca di masa depan.
Steve Jessop
@SteveJessop: Tepat. Baris saya tentang pengembangan vs produksi dimaksudkan untuk menyampaikan gagasan serupa. Kode seharusnya tidak peduli tentang apa yang ada dalam memori tidak diinisialisasi di luar gagasan yang samar-samar tentang "Keacakan mungkin menyenangkan". Jika perilaku program dipengaruhi oleh isi satu keping memori yang tidak diinisialisasi, isi dari keping-keping yang diperoleh di masa depan mungkin akan terpengaruh oleh itu.
supercat
5

Seperti yang orang lain katakan, itu akan cepat, tetapi tidak acak.

Apa yang kebanyakan kompiler akan lakukan untuk variabel lokal adalah untuk mengambil beberapa ruang untuk mereka di stack, tetapi tidak repot mengaturnya untuk apa pun (standar mengatakan mereka tidak perlu, jadi mengapa memperlambat kode yang Anda hasilkan?).

Dalam hal ini, nilai yang Anda dapatkan akan tergantung pada apa yang sebelumnya ada di stack - jika Anda memanggil fungsi sebelum ini yang memiliki seratus variabel char lokal semua diatur ke 'Q' dan kemudian memanggil fungsi Anda setelah yang kembali, maka Anda mungkin akan menemukan nilai-nilai "acak" Anda berperilaku seolah-olah Anda memiliki memset()semuanya untuk 'Q's.

Yang penting untuk fungsi contoh Anda mencoba menggunakan ini, nilai-nilai ini tidak akan berubah setiap kali Anda membacanya, mereka akan sama setiap kali. Jadi Anda akan mendapatkan 100 bintang yang diatur dengan warna dan visibilitas yang sama.

Juga, tidak ada yang mengatakan bahwa kompiler tidak boleh menginisialisasi nilai ini - jadi kompiler masa depan mungkin melakukannya.

Secara umum: ide buruk, jangan lakukan itu. (seperti banyak optimasi tingkat kode "pintar" benar-benar ...)

Alun Thomas
sumber
2
Anda membuat beberapa prediksi kuat tentang apa yang akan terjadi meskipun tidak ada yang dijamin karena UB. Itu juga tidak benar dalam praktik.
usr
3

Seperti yang telah disebutkan orang lain, ini adalah perilaku tidak terdefinisi ( UB ), tetapi mungkin "berhasil".

Kecuali dari masalah yang telah disebutkan oleh orang lain, saya melihat satu masalah lain (kerugian) - itu tidak akan berfungsi dalam bahasa apa pun selain C dan C ++. Saya tahu bahwa pertanyaan ini adalah tentang C ++, tetapi jika Anda dapat menulis kode yang akan menjadi C ++ dan kode Java yang baik dan itu bukan masalah maka mengapa tidak? Mungkin suatu hari seseorang harus mem-port-nya ke bahasa lain dan mencari bug yang disebabkan oleh "trik sulap" UB seperti ini pasti akan menjadi mimpi buruk (terutama untuk pengembang C / C ++ yang tidak berpengalaman).

Di sini ada pertanyaan tentang UB serupa lainnya. Bayangkan saja Anda mencoba menemukan bug seperti ini tanpa mengetahui tentang UB ini. Jika Anda ingin membaca lebih lanjut tentang hal-hal aneh di C / C ++, baca jawaban untuk pertanyaan dari tautan dan lihat slideshow BESAR ini . Ini akan membantu Anda memahami apa yang ada di balik tudung dan bagaimana cara kerjanya; ini bukan hanya tampilan slide lain yang penuh dengan "sihir". Saya cukup yakin bahwa sebagian besar programmer C / c ++ yang berpengalaman dapat belajar banyak dari ini.

cyriel
sumber
3

Bukan ide yang baik untuk mengandalkan logika apa pun pada perilaku bahasa yang tidak terdefinisi. Selain apa pun yang disebutkan / dibahas dalam posting ini, saya ingin menyebutkan bahwa dengan pendekatan C ++ modern / gaya program seperti itu mungkin tidak dapat dikompilasi.

Ini disebutkan dalam posting saya sebelumnya yang berisi keunggulan fitur otomatis dan tautan bermanfaat untuk hal yang sama.

https://stackoverflow.com/a/26170069/2724703

Jadi, jika kita mengubah kode di atas dan mengganti tipe aktual dengan otomatis , program bahkan tidak dapat dikompilasi.

void updateEffect(){
    for(int i=0;i<1000;i++){
        auto r;
        auto g;
        auto b;
        star[i].setColor(r%255,g%255,b%255);
        auto isVisible;
        star[i].setVisible(isVisible);
    }
}
Mantosh Kumar
sumber
3

Saya suka cara berpikir Anda. Benar-benar di luar kotak. Namun tradeoff benar-benar tidak sepadan. Memory-runtime tradeoff adalah sesuatu, termasuk perilaku tidak terdefinisi untuk runtime tidak .

Itu harus memberi Anda perasaan yang sangat meresahkan untuk mengetahui Anda menggunakan "acak" seperti logika bisnis Anda. Saya tidak akan melakukannya.

DDan
sumber
3

Gunakan 7757setiap tempat Anda tergoda untuk menggunakan variabel yang tidak diinisialisasi. Saya mengambilnya secara acak dari daftar bilangan prima:

  1. itu didefinisikan perilaku

  2. dijamin tidak selalu 0

  3. itu prima

  4. itu mungkin secara statistik acak sebagai variabel tidak terinualisasi

  5. itu cenderung lebih cepat daripada variabel tidak diinisialisasi karena nilainya diketahui pada waktu kompilasi

Glenn Teitelbaum
sumber
Untuk perbandingan, lihat hasilnya dalam jawaban ini: stackoverflow.com/a/31836461/2963099
Glenn Teitelbaum
1

Ada satu lagi kemungkinan untuk dipertimbangkan.

Kompiler modern (ahem g ++) sangat cerdas sehingga mereka menelusuri kode Anda untuk melihat instruksi apa yang mempengaruhi status, dan apa yang tidak, dan jika sebuah instruksi dijamin TIDAK mempengaruhi status, g ++ hanya akan menghapus instruksi itu.

Jadi, inilah yang akan terjadi. g ++ pasti akan melihat bahwa Anda membaca, menjalankan aritmatika, menyimpan, apa yang pada dasarnya merupakan nilai sampah, yang menghasilkan lebih banyak sampah. Karena tidak ada jaminan bahwa sampah baru lebih berguna daripada yang lama, itu hanya akan menghilangkan loop Anda. BLOOP!

Metode ini berguna, tetapi inilah yang akan saya lakukan. Gabungkan UB (Undefined Behavior) dengan rand () speed.

Tentu saja, kurangi rand() s dieksekusi, tetapi campur mereka jadi compiler tidak melakukan apa pun yang tidak Anda inginkan.

Dan aku tidak akan memecatmu.

ps95
sumber
Saya merasa sangat sulit untuk percaya kompiler dapat memutuskan kode Anda melakukan sesuatu yang konyol dan menghapusnya. Saya berharap itu hanya untuk mengoptimalkan kode yang tidak terpakai , bukan kode yang tidak disarankan . Apakah Anda memiliki test case yang dapat direproduksi? Either way, rekomendasi dari UB berbahaya. Plus, GCC bukan satu-satunya kompiler yang kompeten, jadi tidak adil untuk memilihnya sebagai "modern".
underscore_d
-1

Menggunakan data yang tidak diinisialisasi untuk keacakan tidak selalu merupakan hal yang buruk jika dilakukan dengan benar. Faktanya, OpenSSL melakukan hal ini untuk menaburkan PRNG-nya.

Tampaknya penggunaan ini tidak didokumentasikan dengan baik, karena seseorang memperhatikan Valgrind mengeluh tentang penggunaan data yang tidak diinisialisasi dan "memperbaikinya", menyebabkan bug pada PRNG .

Jadi Anda bisa melakukannya, tetapi Anda perlu tahu apa yang Anda lakukan dan memastikan bahwa siapa pun yang membaca kode Anda memahami hal ini.

dbush
sumber
1
Ini akan tergantung pada kompiler Anda yang diharapkan dengan perilaku tidak terdefinisi, seperti yang dapat kita lihat dari jawaban saya, dentang hari ini tidak akan melakukan apa yang mereka inginkan.
Shafik Yaghmour
6
OpenSSL yang menggunakan metode ini sebagai input entropi tidak mengatakan bahwa itu bagus. Bagaimanapun, satu-satunya sumber entropi lain yang mereka gunakan adalah PID . Bukan nilai acak yang bagus. Dari seseorang yang mengandalkan sumber entropi yang buruk, saya tidak akan mengharapkan penilaian yang baik pada sumber entropi lainnya. Saya hanya berharap, orang-orang yang saat ini memelihara OpenSSL lebih cerah.
cmaster - mengembalikan monica