Koncepcja tych czterech linii skomplikowanego kodu C

Dlaczego ten kod daje wyjście C++Sucks? Co za tym stoi?

#include <stdio.h>

double m[] = {7709179928849219.0, 771};

int main() {
    m[1]--?m[0]*=2,main():printf((char*)m);    
}

Przetestuj to tutaj .

Author: Adam Stelmaszczyk, 2013-08-01

9 answers

Liczba 7709179928849219.0 ma następującą reprezentację binarną jako 64-bitowa double:

01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------

+ pokazuje położenie znaku; ^ wykładnika i - mantysy (tzn. wartość bez wykładnika).

Ponieważ reprezentacja używa wykładnika binarnego i mantysy, podwajanie liczby zwiększa wykładnik o jeden. Twój program robi to dokładnie 771 razy, więc wykładnik, który rozpoczął się przy 1075 (reprezentacja dziesiętna 10000110011) staje się 1075 + 771 = 1846 przy koniec; binarna reprezentacja 1846 to 11100110110. Wynikowy wzór wygląda tak:

01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'

Ten wzór odpowiada wydrukowanemu łańcuchowi, tylko do tyłu. W tym samym czasie drugi element tablicy staje się zerem, zapewniając Terminator null, dzięki czemu łańcuch nadaje się do przejścia do printf().

 488
Author: dasblinkenlight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-23 17:42:56

Bardziej czytelna Wersja:

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);
    }
}

Wywołuje rekurencyjnie main() 771 razy.

Na początku m[0] = 7709179928849219.0, które oznacza dla C++Suc;C. W każdym wywołaniu m[0] zostaje podwojona, aby "naprawić" ostatnie dwie litery. W ostatnim wywołaniu m[0] zawiera reprezentację znaku ASCII C++Sucks i m[1] zawiera tylko zera, więc ma null terminator dla C++Sucks string. Wszystko przy założeniu, że m[0] jest przechowywany na 8 bajtach, więc każdy znak zajmuje 1 bajt.

Bez rekurencji i nielegalnemain() wywołanie tego będzie wyglądało tak:

double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
    m[0] *= 2;
}
printf((char*) m);
 214
Author: Adam Stelmaszczyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-18 17:38:02

Disclaimer: Ta odpowiedź została zamieszczona w oryginalnej formie pytania, które dotyczyło tylko C++ i zawierało nagłówek C++. Konwersja pytania do czystego C została wykonana przez społeczność, bez wkładu pierwotnego asker.


Formalnie rzecz biorąc, nie jest możliwe rozumowanie o tym programie, ponieważ jest źle uformowany (tzn. nie jest legalnym C++). Narusza C++11 [basic.zaczynaj.główna] p3:

Funkcja main nie może być używana w program.

Pomijając to, opiera się na fakcie, że na typowym komputerze konsumenckim double ma długość 8 bajtów i wykorzystuje pewną dobrze znaną wewnętrzną reprezentację. Początkowe wartości tablicy są obliczane tak, że podczas wykonywania "algorytmu", ostateczna wartość pierwszego double będzie taka, że reprezentacja wewnętrzna (8 bajtów) będzie kodami ASCII 8 znaków C++Sucks. Drugim elementem tablicy jest wtedy 0.0, którego pierwszy bajt to 0 w wewnętrznej reprezentacji, czyniąc to prawidłowym ciągiem w stylu C. To jest następnie wysyłane do wyjścia za pomocą printf().

Uruchomienie tego na serwerze sprzętowym, gdzie niektóre z powyższych nie działają, zamiast tego spowodowałoby tekst śmieci (a może nawet dostęp poza granicami).

 103
Author: Angew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-16 09:17:57

Być może najprostszym sposobem na zrozumienie kodu jest praca na odwrót. Zaczniemy od napisu do wydrukowania -- dla równowagi użyjemy "C++Rocks". Kluczowy punkt: podobnie jak oryginał, ma dokładnie osiem znaków. Ponieważ zamierzamy zrobić (mniej więcej) jak oryginał i wydrukować go w odwrotnej kolejności, zaczniemy od umieszczenia go w odwrotnej kolejności. W pierwszym kroku przejrzymy ten wzorzec bitowy jako double i wydrukujemy wynik:

#include <stdio.h>

char string[] = "skcoR++C";

int main(){
    printf("%f\n", *(double*)string);
}

To produkuje 3823728713643449.5. Chcemy więc manipulować tym w sposób, który nie jest oczywisty, ale jest łatwy do odwrócenia. W połowie dowolnie wybieram mnożenie przez 256, co daje nam 978874550692723072. Teraz musimy napisać jakiś zaciemniony kod, aby podzielić przez 256, a następnie wydrukować poszczególne bajty tego w odwrotnej kolejności:

#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);
}

Teraz mamy wiele casting, przekazywanie argumentów do (rekurencyjnych) main, które są całkowicie ignorowane (ale ocena, aby uzyskać przyrost i dekrement są absolutnie kluczowe), i oczywiście ta całkowicie arbitralna Liczba, aby zatuszować fakt, że to, co robimy, jest naprawdę dość proste.

Oczywiście, ponieważ chodzi o zaciemnienie, jeśli mamy na to ochotę, możemy również podjąć więcej kroków. Na przykład, możemy skorzystać z oceny zwarciowej, aby przekształcić nasze if oświadczenie w pojedyncze wyrażenie, więc ciało main wygląda tak:

x[1] && (x[0] /= 2,  main(--x[1], (char **)++y));
putchar(*--y);

Do każdego, kto nie jest przyzwyczajony do zaciemnionego kodu (i / lub kodu golf) to zaczyna wyglądać dość dziwnie-obliczanie i odrzucanie logicznej and jakiejś bezsensownej liczby zmiennoprzecinkowej i zwracanej wartości z main, która nawet nie zwraca wartości. Co gorsza, nie zdając sobie sprawy (i nie myśląc o), jak działa ocena zwarcia, może nawet nie być od razu oczywiste, w jaki sposób unika się nieskończonej rekurencji.

Naszym następnym krokiem będzie prawdopodobnie oddzielenie drukowania każdego znaku od znalezienia tego znaku. Możemy to zrobić całkiem łatwo przez generowanie WŁAŚCIWEGO znaku jako wartości zwracanej z main i wypisywanie tego, co zwraca main:

x[1] && (x[0] /= 2,  putchar(main(--x[1], (char **)++y)));
return *--y;
Przynajmniej dla mnie, to wydaje się dość zaciemnione, więc zostawię to na tym.
 55
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-03 17:54:51

Jest to po prostu budowanie podwójnej tablicy (16 bajtów), która-jeśli zostanie zinterpretowana jako tablica znaków-buduje kody ASCII Dla ciągu "C++Sucks"

Jednak kod nie działa na każdym systemie, opiera się na niektórych z następujących niezdefiniowanych faktów:

 22
Author: D.R.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-17 10:40:39

Następujący kod drukuje C++Suc;C, więc całe mnożenie jest tylko dla dwóch ostatnich liter

double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
 10
Author: Serve Laurijssen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-01 12:05:54

Inni wyjaśnili pytanie dość dokładnie, chciałbym dodać notatkę, że jest to nieokreślone zachowanie zgodnie ze standardem.

C++11 3.6.1/3 Main function

Funkcja main nie może być używana w programie. Powiązanie (3.5) main jest zdefiniowane w implementacji. Program, który definiuje main jako usunięty lub deklaruje main jako inline, static lub constexpr, jest źle utworzony. Nazwa main nie jest zarezerwowana w inny sposób. [Przykład: funkcje członkowskie, klasy i wyliczenia mogą być nazywane main, podobnie jak encje w innych przestrzeniach nazw. -end example]

 9
Author: Yu Hao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-01 14:06:25

Kod można przepisać Tak:

void f()
{
    if (m[1]-- != 0)
    {
        m[0] *= 2;
        f();
    } else {
          printf((char*)m);
    }
}

Tworzy zestaw bajtów w tablicy double m, które odpowiadają znakom "C++Sucks", po których następuje null-terminator. Zakodowali kod wybierając podwójną wartość, która po podwojeniu 771 razy daje, w standardowej reprezentacji, ten zestaw bajtów z terminatorem null dostarczonym przez drugi człon tablicy.

Zauważ, że ten kod nie będzie działał pod innym reprezentacja endyjska Ponadto wywołanie main() nie jest ściśle dozwolone.

 8
Author: Jack Aidley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-02 16:12:46

Jest to po prostu sprytny sposób na ukrycie ciągu "C++Sucks" (zwróć uwagę na 8 bajtów) w pierwszej podwójnej wartości, która jest rekurencyjnie pomnożona przez dwa, aż podwójne wartości sekund osiągną zero (771 razy).

Mnożenie wartości podwójnych 7709179928849219.0 * 2 * 711 powoduje to" C++Sucks", jeśli zinterpretujesz wartość bajtu double jako ciąg znaków, co printf () robi z cast. I printf () nie zawodzi, ponieważ druga Podwójna wartość to " 0 "i zinterpretowana jako" \0 " przez printf ().

 -2
Author: Sir Pancake,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-03 17:53:06