Mengapa kode ini memberikan hasil C++Sucks
? Apa konsep di baliknya?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Uji di sini .
skcuS++C
.
Mengapa kode ini memberikan hasil C++Sucks
? Apa konsep di baliknya?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Uji di sini .
skcuS++C
.
Jawaban:
Nomor tersebut 7709179928849219.0
memiliki representasi biner berikut sebagai 64-bit double
:
01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------
+
menunjukkan posisi tanda; ^
dari eksponen, dan -
mantissa (yaitu nilai tanpa eksponen).
Karena representasi menggunakan eksponen biner dan mantissa, menggandakan jumlah akan menambah eksponen dengan satu. Program Anda melakukannya tepat 771 kali, sehingga eksponen yang dimulai pada 1075 (representasi desimal 10000110011
) menjadi 1075 + 771 = 1846 pada akhirnya; representasi biner tahun 1846 adalah 11100110110
. Pola yang dihasilkan terlihat seperti ini:
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'
Pola ini sesuai dengan string yang Anda lihat dicetak, hanya mundur. Pada saat yang sama, elemen kedua array menjadi nol, memberikan terminator nol, membuat string cocok untuk diteruskan printf()
.
7709179928849219
nilainya, dan mendapatkan representasi biner kembali.
Versi yang lebih mudah dibaca:
double m[2] = {7709179928849219.0, 771};
// m[0] = 7709179928849219.0;
// m[1] = 771;
int main()
{
if (m[1]-- != 0)
{
m[0] *= 2;
main();
}
else
{
printf((char*) m);
}
}
Itu secara rekursif memanggil main()
771 kali.
Pada awalnya, m[0] = 7709179928849219.0
yang berdiri untuk C++Suc;C
. Dalam setiap panggilan, m[0]
digandakan, untuk "memperbaiki" dua huruf terakhir. Dalam panggilan terakhir, m[0]
berisi representasi karakter ASCII C++Sucks
dan m[1]
hanya berisi nol, sehingga memiliki terminator nol untuk C++Sucks
string. Semua dalam asumsi yang m[0]
disimpan pada 8 byte, sehingga masing-masing karakter membutuhkan 1 byte.
Tanpa rekursi dan main()
panggilan ilegal akan terlihat seperti ini:
double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
m[0] *= 2;
}
printf((char*) m);
Penafian: Jawaban ini diposting ke bentuk asli dari pertanyaan, yang hanya menyebutkan C ++ dan menyertakan header C ++. Konversi pertanyaan menjadi murni C dilakukan oleh komunitas, tanpa masukan dari penanya semula.
Secara formal, tidak mungkin untuk beralasan tentang program ini karena programnya tidak lengkap (artinya, ini bukan C ++ legal) Itu melanggar C ++ 11 [basic.start.main] p3:
Fungsi utama tidak boleh digunakan dalam suatu program.
Selain itu, ini bergantung pada fakta bahwa pada komputer konsumen tipikal, double
panjangnya 8 byte, dan menggunakan representasi internal tertentu yang terkenal. Nilai awal array dihitung sehingga ketika "algoritma" dilakukan, nilai akhir yang pertama double
akan sedemikian rupa sehingga representasi internal (8 byte) akan menjadi kode ASCII dari 8 karakter C++Sucks
. Elemen kedua dalam array adalah 0.0
byte pertama yang berada 0
dalam representasi internal, menjadikannya string gaya C yang valid. Ini kemudian dikirim ke keluaran menggunakan printf()
.
Menjalankan ini di HW di mana beberapa hal di atas tidak berlaku akan menghasilkan teks sampah (atau bahkan mungkin akses di luar batas) sebagai gantinya.
basic.start.main
3.6.1 / 3 dengan kata-kata yang sama.
main()
, atau menggantinya dengan panggilan API untuk memformat harddisk, atau apa pun.
Mungkin cara termudah untuk memahami kode adalah bekerja melalui hal-hal secara terbalik. Kami akan mulai dengan string untuk mencetak - untuk keseimbangan, kami akan menggunakan "C ++ Rocks". Poin penting: sama seperti aslinya, panjangnya persis delapan karakter. Karena kita akan melakukan (kira-kira) seperti aslinya, dan mencetaknya dalam urutan terbalik, kita akan mulai dengan meletakkannya dalam urutan terbalik. Untuk langkah pertama kami, kami hanya akan melihat pola bit itu sebagai double
, dan mencetak hasilnya:
#include <stdio.h>
char string[] = "skcoR++C";
int main(){
printf("%f\n", *(double*)string);
}
Ini menghasilkan 3823728713643449.5
. Jadi, kami ingin memanipulasi itu dengan cara yang tidak jelas, tetapi mudah untuk dibalik. Saya akan semi-sewenang-wenang memilih perkalian dengan 256, yang memberi kita 978874550692723072
. Sekarang, kita hanya perlu menulis beberapa kode yang dikaburkan untuk dibagi dengan 256, lalu mencetak masing-masing byte dengan urutan terbalik:
#include <stdio.h>
double x [] = { 978874550692723072, 8 };
char *y = (char *)x;
int main(int argc, char **argv){
if (x[1]) {
x[0] /= 2;
main(--x[1], (char **)++y);
}
putchar(*--y);
}
Sekarang kami memiliki banyak casting, memberikan argumen kepada (rekursif) main
yang sepenuhnya diabaikan (tetapi evaluasi untuk mendapatkan kenaikan dan penurunan sangat penting), dan tentu saja angka yang benar-benar sewenang-wenang mencari untuk menutupi fakta bahwa apa yang kita lakukan benar-benar sangat mudah.
Tentu saja, karena intinya adalah kebingungan, jika kita merasa kita dapat mengambil lebih banyak langkah juga. Sebagai contoh, kita dapat mengambil keuntungan dari evaluasi hubung singkat, untuk mengubah if
pernyataan kita menjadi satu ekspresi, sehingga badan utama terlihat seperti ini:
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
Bagi siapa pun yang tidak terbiasa dengan kode yang dikaburkan (dan / atau kode golf) ini mulai terlihat sangat aneh - menghitung dan membuang logika and
beberapa angka floating point yang tidak berarti dan nilai pengembalian dari main
, yang bahkan tidak mengembalikan nilai. Lebih buruk lagi, tanpa menyadari (dan berpikir tentang) bagaimana evaluasi hubung singkat bekerja, bahkan mungkin tidak segera jelas bagaimana hal itu menghindari rekursi tak terbatas.
Langkah kami berikutnya mungkin akan memisahkan mencetak setiap karakter dari menemukan karakter itu. Kita dapat melakukannya dengan cukup mudah dengan menghasilkan karakter yang tepat sebagai nilai pengembalian main
, dan mencetak apa yang main
dikembalikan:
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
Setidaknya bagi saya, itu tampaknya cukup membingungkan, jadi saya akan berhenti di situ.
Itu hanya membangun array ganda (16 byte) yang - jika diartikan sebagai array char - membangun kode ASCII untuk string "C ++ Sucks"
Namun, kode ini tidak berfungsi pada setiap sistem, itu bergantung pada beberapa fakta tidak terdefinisi berikut:
Kode berikut dicetak C++Suc;C
, jadi seluruh perkalian hanya untuk dua huruf terakhir
double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
Yang lain telah menjelaskan pertanyaan dengan cukup teliti, saya ingin menambahkan catatan bahwa ini adalah perilaku yang tidak terdefinisi menurut standar.
C ++ 11 3.6.1 / 3 Fungsi utama
Fungsi utama tidak boleh digunakan dalam suatu program. Linkage (3.5) dari main ditentukan oleh implementasi. Program yang mendefinisikan main sebagai dihapus atau yang menyatakan main sebagai inline, static, atau constexpr adalah salah bentuk. Nama utama tidak dinyatakan dilindungi undang-undang. [Contoh: fungsi anggota, kelas, dan enumerasi dapat disebut main, seperti halnya entitas di ruang nama lain. —Kirim contoh]
Kode dapat ditulis ulang seperti ini:
void f()
{
if (m[1]-- != 0)
{
m[0] *= 2;
f();
} else {
printf((char*)m);
}
}
Apa yang dilakukannya adalah menghasilkan satu set byte dalam double
array m
yang sesuai dengan karakter 'C ++ Sucks' diikuti oleh null-terminator. Mereka mengaburkan kode dengan memilih nilai ganda yang ketika digandakan 771 kali menghasilkan, dalam representasi standar, set byte dengan terminator nol yang disediakan oleh anggota kedua array.
Perhatikan bahwa kode ini tidak akan berfungsi di bawah representasi endian yang berbeda. Selain itu, panggilan main()
tidak diijinkan.
f
pengembalian Anda int
?
int
kembalinya dalam pertanyaan. Biarkan saya memperbaikinya.
Pertama-tama kita harus ingat bahwa angka presisi ganda disimpan dalam memori dalam format biner sebagai berikut:
(i) 1 bit untuk tanda
(ii) 11 bit untuk eksponen
(iii) 52 bit untuk besarnya
Urutan bit menurun dari (i) ke (iii).
Pertama bilangan pecahan desimal dikonversi menjadi bilangan biner pecahan ekivalen dan kemudian dinyatakan sebagai urutan besarnya dalam biner.
Jadi angka 7709179928849219.0 menjadi
(11011011000110111010101010011001010110010101101000011)base 2
=1.1011011000110111010101010011001010110010101101000011 * 2^52
Sekarang sambil mempertimbangkan bit magnitudo 1. diabaikan karena semua urutan metode magnitudo akan dimulai dengan 1.
Jadi bagian besarnya menjadi:
1011011000110111010101010011001010110010101101000011
Sekarang kekuatan 2 adalah 52 , kita perlu menambahkan angka bias sebagai 2 ^ (bit untuk eksponen -1) -1 yaitu 2 ^ (11 -1) -1 = 1023 , sehingga eksponen kita menjadi 52 + 1023 = 1075
Sekarang kode kita memutipkan angka dengan 2 , 771 kali yang membuat eksponen meningkat sebesar 771
Jadi eksponen kami adalah (1075 + 771) = 1846 yang setara binernya adalah (11100110110)
Sekarang angka kita positif sehingga bit tanda kita adalah 0 .
Jadi nomor kami yang diubah menjadi:
bit tanda + eksponen + magnitudo (penggabungan bit-bit sederhana)
0111001101101011011000110111010101010011001010110010101101000011
karena m dikonversi menjadi pointer char kita akan membagi pola bit dalam potongan 8 dari LSD
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
(yang setara Hex adalah :)
0x73 0x6B 0x63 0x75 0x53 0x2B 0x2B 0x43
Yang dari peta karakter seperti yang ditunjukkan adalah:
s k c u S + + C
Sekarang setelah ini dibuat m [1] adalah 0 yang berarti karakter NULL
Sekarang dengan asumsi bahwa Anda menjalankan program ini pada mesin little-endian (bit urutan lebih rendah disimpan di alamat yang lebih rendah) jadi pointer m pointer ke bit alamat terendah dan kemudian melanjutkan dengan mengambil bit dalam chuck 8 (seperti tipe yang dicor ke char * ) dan printf () berhenti ketika menemukan 00000000 di chunck terakhir ...
Namun kode ini tidak portabel.