Bu kod neden çıktıyı veriyor C++Sucks
? Arkasındaki kavram nedir?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Burada test edin .
skcuS++C
.
Bu kod neden çıktıyı veriyor C++Sucks
? Arkasındaki kavram nedir?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Burada test edin .
skcuS++C
.
Yanıtlar:
Sayı 7709179928849219.0
64 bit olarak aşağıdaki ikili gösterime sahiptir double
:
01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------
+
işaretin konumunu gösterir; ^
üssü ve -
mantis (yani üssü olmayan değer).
Temsili ikili üs ve mantis kullandığından, sayının ikiye katlanması üssü bir arttırır. Programınız tam olarak 771 kez yapar, böylece 1075'te başlayan (ondalık temsili 10000110011
) üs sonunda 1075 + 771 = 1846 olur; 1846 ikili gösterimidir 11100110110
. Ortaya çıkan desen şöyle görünür:
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'
Bu desen, yalnızca geriye doğru yazdırıldığını gördüğünüz dizeye karşılık gelir. Aynı zamanda, dizinin ikinci öğesi sıfır olur, boş sonlandırıcı sağlar ve dizeyi geçmeye uygun hale getirir printf()
.
7709179928849219
Değeri yapıştırdım ve ikili temsili geri aldım.
Daha okunabilir versiyon:
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);
}
}
Özyinelemeli olarak main()
771 kez arar .
Başlangıçta, m[0] = 7709179928849219.0
hangi standları için C++Suc;C
. Her çağrıda, m[0]
son iki harfi "onarmak" için iki katına çıkar. Son çağrıda, m[0]
ASCII karakter temsilini içerir C++Sucks
ve m[1]
yalnızca sıfır içerir, bu nedenle dize için boş bir sonlandırıcıya sahiptir C++Sucks
. Hepsi m[0]
8 baytta saklandığı varsayımıyla , her karakter 1 bayt alır.
Özyineleme ve yasadışı main()
arama olmadan şöyle görünecektir:
double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
m[0] *= 2;
}
printf((char*) m);
Feragatname: Bu cevap, sorunun sadece C ++ 'dan bahseden ve bir C ++ üstbilgisi içeren orijinal formuna gönderildi. Sorunun saf C'ye dönüşümü, orijinal askerin girdisi olmadan topluluk tarafından yapıldı.
Resmi olarak konuşursak, bu program hakkında akıl yürütmek imkansız çünkü kötü biçimlendirilmiş (yani yasal C ++ değil). C ++ 11 [basic.start.main] p3'ü ihlal eder:
Main işlevi bir programda kullanılmamalıdır.
Bu bir yana, tipik bir tüketici bilgisayarında, double
a'nın 8 bayt uzunluğunda olduğu ve belirli bir iyi bilinen iç temsili kullandığı gerçeğine dayanır . Dizinin başlangıç değerleri, "algoritma" gerçekleştirildiğinde, birincinin son değeri double
, iç gösterim (8 bayt) 8 karakterin ASCII kodları olacak şekilde hesaplanır C++Sucks
. Bu durumda dizideki ikinci öğe 0.0
, ilk baytı 0
dahili gösterimde olup, bunu geçerli bir C stili dize yapar. Bu daha sonra kullanılarak çıktıya gönderilir printf()
.
Bunu, yukarıdakilerin bazılarının beklemediği HW'de çalıştırmak, bunun yerine çöp metniyle (hatta sınırların dışında bir erişimle) sonuçlanır.
basic.start.main
aynı ifadeye sahip 3.6.1 / 3 vardı .
main()
.
Belki de kodu anlamanın en kolay yolu şeyleri tersine çevirmektir. Yazdırmak için bir dize ile başlayacağız - denge için "C ++ Rocks" kullanacağız. Önemli nokta: tıpkı orijinali gibi, tam sekiz karakter uzunluğunda. Orijinali (kabaca) yapacağız ve ters sırada yazacağımız için, ters sırayla koyarak başlayacağız. İlk adımımız için, bu bit desenini a olarak göreceğiz double
ve sonucu yazdıracağız:
#include <stdio.h>
char string[] = "skcoR++C";
int main(){
printf("%f\n", *(double*)string);
}
Bu üretir 3823728713643449.5
. Bu yüzden, bunu açık olmayan ancak tersine çevrilmesi kolay olan bir şekilde manipüle etmek istiyoruz. Yarı keyfi olarak 256 ile çarpmayı seçeceğim, bu da bize veriyor 978874550692723072
. Şimdi, 256'ya bölmek için bazı gizli kodlar yazmamız ve ardından bunun ayrı ayrı baytlarını ters sırada yazdırmamız gerekiyor:
#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);
}
Şimdi main
, tamamen göz ardı edilen (özyinelemeli) argümanlara çok sayıda döküm yaptık (ancak artışı ve azalmayı elde etmek için değerlendirme tamamen önemlidir) ve elbette yaptığımız gerçeği örtbas etmek için tamamen keyfi görünümlü sayı gerçekten çok basit.
Tabii ki, bütün mesele gizleme olduğundan, eğer öyle hissedersek, daha fazla adım da atabiliriz. Örneğin, if
ifademizi tek bir ifadeye dönüştürmek için kısa devre değerlendirmesinden yararlanabiliriz , böylece ana gövde şöyle görünür:
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
Mantıksal işlem atarak - bu başlar aslında oldukça garip bakmaya Karartılmış kodu (ve / veya kod golf) alışık olmayan kimseye and
nokta numarası ve dönüş değeri yüzen bazı Anlamsız main
bile a dönmüyor, değer. Daha da kötüsü, kısa devre değerlendirmesinin nasıl çalıştığını fark etmeden (ve düşünmeden), sonsuz özyinelemeden nasıl kaçınıldığı hemen belli olmayabilir.
Bir sonraki adımımız muhtemelen her karakteri yazdırmayı o karakteri bulmaktan ayırmak olacaktır. Geri dönüş değeri olarak doğru karakteri oluşturarak ve geri dönenleri main
yazdırarak bunu kolayca yapabiliriz main
:
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
En azından bana, bu yeterince şaşırmış görünüyor, bu yüzden onu bırakacağım.
Aşağıdaki kod yazdırılır C++Suc;C
, böylece tüm çarpma işlemi yalnızca son iki harf içindir
double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
Diğerleri soruyu oldukça ayrıntılı bir şekilde açıkladılar, bunun standarda göre tanımlanmamış bir davranış olduğunu not etmek istiyorum .
C ++ 11 3.6.1 / 3 Ana işlev
Main işlevi bir programda kullanılmamalıdır. Ana bağlantı (3.5) uygulama tanımlıdır. Ana öğeyi silinmiş olarak tanımlayan veya ana öğenin satır içi, statik veya bağlamsal olduğunu bildiren bir program kötü biçimlendirilmiştir. Main adı başka şekilde ayrılmış değildir. [Örnek: üye işlevleri, sınıflar ve numaralandırmalar, diğer ad alanlarındaki varlıklar gibi ana olarak adlandırılabilir. —End örneği]
Kod şu şekilde yeniden yazılabilir:
void f()
{
if (m[1]-- != 0)
{
m[0] *= 2;
f();
} else {
printf((char*)m);
}
}
Ne yapıyor bayt bir dizi üreten bir double
dizide m
bir boşlukla terminatör ardından karakterlerin C ++ Sucks 'olarak eşleşecek şekilde olur. 771 kez ikiye katlandığında, standart gösterimde, dizinin ikinci üyesi tarafından sağlanan boş sonlandırıcı ile bu bayt kümesini üreten bir çift değer seçerek kodu gizlemişlerdir.
Bu kodun farklı bir endian temsili altında çalışmayacağını unutmayın. Ayrıca, çağrıya main()
kesinlikle izin verilmez.
f
Geri dönüşünüz neden bir int
?
int
sorudaki getiriyi kopyalarken beyinsizdim . Bunu düzeltmeme izin ver.
İlk olarak, çift duyarlıklı sayıların bellekte ikili biçimde saklandığını hatırlamalıyız:
(i) işaret için 1 bit
(ii) üs için 11 bit
(iii) Büyüklük için 52 bit
Bitlerin sırası (i) 'den (iii)' e düşer.
İlk olarak ondalık kesirli sayı, eşdeğer kesirli ikili sayıya dönüştürülür ve sonra ikili olarak büyüklük sırası olarak ifade edilir.
Böylece 7709179928849219.0 sayısı olur
(11011011000110111010101010011001010110010101101000011)base 2
=1.1011011000110111010101010011001010110010101101000011 * 2^52
Şimdi, büyüklük bitleri 1. dikkate alındığında, tüm büyüklük yöntemi 1 ile başlayacağı için ihmal edilir .
Böylece büyüklük kısmı:
1011011000110111010101010011001010110010101101000011
Şimdi gücü 2 olduğu 52 , biz bu numarayı polarizasyon eklemek gerekir 2 ^ (üs için bit-1) -1 , yani 2 ^ (11 -1) -1 = 1023 eden üs hale gelir, böylece, 52 + 1023 = 1075
Şimdi kodumuz sayıyı 2 , 771 kez siler ve bu da üssü 771 arttırır.
Yani üssümüz (1075 + 771) = 1846 , ikili eşdeğeri (11100110110)
Şimdi sayımız pozitif, yani işaret bitimiz 0 .
Böylece değiştirilmiş numaramız:
işaret biti + üs + büyüklük (bitlerin basit birleşimi)
0111001101101011011000110111010101010011001010110010101101000011
m karakter işaretçisine dönüştürüldüğünden, 8 parçasındaki bit desenini LSD'den ayırırız
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
(Hex eşdeğeri olan :)
0x73 0x6B 0x63 0x75 0x53 0x2B 0x2B 0x43
Karakter haritasından gösterildiği gibi:
s k c u S + + C
Şimdi bu yapıldıktan sonra m [1] 0'dır, bu da NULL karakter anlamına gelir
Şimdi bu programı küçük endian bir makinede çalıştırdığınızı varsayarsak (alt sıra biti alt adreste saklanır) böylece işaretçi m işaretçisi en alt adres bitine işaret eder ve daha sonra 8 chucks'taki bitleri alarak (char * ) ve printf (), son yığında 00000000 ile karşılaştığında durur ...
Ancak bu kod taşınabilir değildir.