이 코드는 왜 출력을 제공 C++Sucks
합니까? 그 뒤에 개념은 무엇입니까?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
skcuS++C
.
이 코드는 왜 출력을 제공 C++Sucks
합니까? 그 뒤에 개념은 무엇입니까?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
skcuS++C
.
답변:
숫자 7709179928849219.0
는 64 비트로 다음 이진 표현을 갖습니다 double
.
01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------
+
기호의 위치를 보여줍니다. ^
지수와 -
가수의 (즉 지수가없는 값).
이 표현은 이진 지수와 가수를 사용하므로 숫자를 두 배로 늘리면 지수가 1 씩 증가합니다. 프로그램은 정확히 771 번 수행하므로 1075 (10 진수로 표시 10000110011
) 에서 시작된 지수 는 1075 + 771 = 1846이됩니다. 1846의 이진 표현은입니다 11100110110
. 결과 패턴은 다음과 같습니다.
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'
이 패턴은 인쇄 된 문자열에 해당하며 뒤로 만 나타납니다. 동시에 배열의 두 번째 요소는 0이되어 null 종결자를 제공하여 문자열을에 전달하기에 적합합니다 printf()
.
7709179928849219
값을 붙여넣고 이진 표현을 다시 얻었습니다.
더 읽기 쉬운 버전 :
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);
}
}
재귀 적으로 main()
771 번 호출합니다 .
처음에는 m[0] = 7709179928849219.0
을 의미 합니다 C++Suc;C
. 모든 통화에서 m[0]
마지막 두 글자를 "수리"하기 위해 두 배가됩니다. 마지막 호출에서 m[0]
의 아스키 문자 표현을 포함 C++Sucks
하고 m[1]
그것이 가지고, 그래서 0 만 포함 널 종결 에 대한 C++Sucks
문자열입니다. m[0]
8 바이트에 저장된 모든 가정하에 각 문자는 1 바이트를 사용합니다.
재귀와 불법 main()
전화가 없으면 다음과 같이 보일 것입니다.
double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
m[0] *= 2;
}
printf((char*) m);
면책 조항 : 이 답변은 C ++ 만 언급하고 C ++ 헤더를 포함하는 원래 형식의 질문에 게시되었습니다. 질문을 순수 C로 변환 한 것은 원래 질문의 입력없이 커뮤니티에서 수행했습니다.
공식적으로 말하면,이 프로그램은 형식이 잘못되어 (즉, 합법적 인 C ++이 아니기 때문에)이 프로그램에 대해 추론 할 수 없습니다. C ++ 11 [basic.start.main] p3을 위반합니다 :
프로그램 내에서 기능 메인을 사용해서는 안됩니다.
이를 제외하고는 일반적인 소비자 컴퓨터에서 double
길이가 8 바이트이며 잘 알려진 특정 내부 표현을 사용 한다는 사실에 의존합니다 . 배열의 초기 값은 "알고리즘"이 수행 double
될 때 첫 번째의 최종 값이 내부 표현 (8 바이트)이 8 자의 ASCII 코드가되도록 계산됩니다 C++Sucks
. 그런 다음 배열의 두 번째 요소는 0.0
입니다. 첫 번째 바이트는 0
내부 표현에 있으므로 유효한 C 스타일 문자열이됩니다. 그런 다음을 사용하여 출력으로 전송됩니다 printf()
.
위의 일부가 유지되지 않는 HW에서 이것을 실행하면 가비지 텍스트 (또는 경계를 벗어난 액세스)가 대신 발생합니다.
basic.start.main
같은 단어로 3.6.1 / 3을 가졌다 .
main()
속합니다.
코드를 이해하는 가장 쉬운 방법은 반대로 작업하는 것입니다. 인쇄 할 문자열로 시작하겠습니다. 균형을 위해 "C ++ Rocks"를 사용합니다. 중요한 점 : 원본과 마찬가지로 정확히 8 자입니다. 원본과 거의 비슷하게 인쇄하고 역순으로 인쇄하기 때문에 역순으로 시작합니다. 첫 번째 단계에서는 비트 패턴을로보고 double
결과를 인쇄합니다.
#include <stdio.h>
char string[] = "skcoR++C";
int main(){
printf("%f\n", *(double*)string);
}
이 생성합니다 3823728713643449.5
. 따라서 우리는 명확하지 않지만 반전하기 쉬운 방식으로 조작하고 싶습니다. 256으로 곱셈을 임의로 임의로 선택하겠습니다 978874550692723072
. 이제 난독 화 코드를 작성하여 256으로 나눈 다음 해당 바이트의 개별 바이트를 역순으로 인쇄하면됩니다.
#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);
}
이제 우리 main
는 완전히 무시 되는 (재귀 적) 인수를 전달하는 많은 캐스팅을 가지고 있지만 (증가 및 감소를 얻는 평가는 완전히 중요합니다) 물론 우리가하고있는 사실을 커버하기 위해 완전히 임의의 숫자 정말 간단합니다.
물론 요점은 난독 화이기 때문에 느낌이 들면 더 많은 조치를 취할 수 있습니다. 예를 들어 단락 평가를 활용하여 if
명령문을 단일 표현식으로 변환 할 수 있으므로 기본 본문은 다음과 같습니다.
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
난독 처리 된 코드 (및 / 또는 코드 골프)에 익숙하지 않은 사람에게는 이것이 실제로 이상한 것처럼 보이기 시작합니다- and
의미없는 부동 소수점 수 의 논리 와main
. 값. 더 나쁜 것은 단락 평가가 어떻게 작동 하는지를 생각하지 않고 생각하지 않고서도 무한 재귀를 피하는 방법을 즉시 알지 못할 수도 있습니다.
다음 단계는 각 문자를 인쇄하는 것과 해당 문자를 찾는 것을 분리하는 것입니다. 에서 올바른 문자를 반환 값으로 생성하고 반환 main
되는 내용을 인쇄하여 쉽게 수행 할 수 있습니다 main
.
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
적어도 나에게는 그것이 난독 화 된 것처럼 보이므로 그것을 그대로 두겠습니다.
다음 코드는 인쇄 C++Suc;C
되므로 전체 곱셈은 마지막 두 글자에만 해당됩니다.
double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
다른 사람들은 질문을 아주 철저하게 설명했습니다 . 표준에 따라 정의되지 않은 동작 이라는 점을 추가하고 싶습니다 .
C ++ 11 3.6.1 / 3 주요 기능
프로그램 내에서 기능 메인을 사용해서는 안됩니다. main의 연결 (3.5)은 구현에 따라 정의됩니다. main을 삭제 된 것으로 정의하거나 main을 인라인, 정적 또는 constexpr로 선언하는 프로그램은 형식이 잘못되었습니다. main이라는 이름은 예약되어 있지 않습니다. [예 : 다른 네임 스페이스의 엔터티와 마찬가지로 멤버 함수, 클래스 및 열거를 main이라고 할 수 있습니다. — 끝 예제]
코드는 다음과 같이 다시 작성할 수 있습니다.
void f()
{
if (m[1]-- != 0)
{
m[0] *= 2;
f();
} else {
printf((char*)m);
}
}
그것이하고 있는 일은 double
배열 에서 바이트 m
'C ++ Sucks'에 해당하는 null 집합이 오는 바이트 세트를 생성하는 것입니다. 그들은 771 배가 두 배가 될 때 표준 표현에서 배열의 두 번째 멤버가 제공하는 null 종결자가있는 바이트 세트를 생성하는 double 값을 선택하여 코드를 난독 화했습니다.
이 코드는 다른 엔디안 표현에서는 작동하지 않습니다. 또한 전화 main()
는 엄격하게 허용되지 않습니다.
f
반환은 int
?
int
그 질문에 대한 답을 머리없이 복사했습니다 . 그 문제를 해결하겠습니다.
먼저 배정도 숫자는 다음과 같이 이진 형식으로 메모리에 저장됩니다.
(i) 부호를위한 1 비트
(ii) 지수에 대한 11 비트
(iii) 크기에 대한 52 비트
비트 순서는 (i)에서 (iii)으로 감소합니다.
먼저 소수 소수는 등가 소수 이진수로 변환 된 다음 이진수로 크기 순서로 표시됩니다.
따라서 숫자 7709179928849219.0 은
(11011011000110111010101010011001010110010101101000011)base 2
=1.1011011000110111010101010011001010110010101101000011 * 2^52
이제 모든 비트 순서 방법이 1로 시작 하므로 크기 비트 1 을 고려하는 동안 무시됩니다 .
따라서 크기 부분은 다음과 같습니다.
1011011000110111010101010011001010110010101101000011
이제 2 의 거듭 제곱 은 52 이므로 2 ^ (지수 -1의 비트) -1 즉 2 ^ (11 -1) -1 = 1023 으로 바이어 싱 수를 추가해야합니다 . 따라서 지수는 52 + 1023 = 1075가됩니다.
이제 우리의 코드는 2 , 771 배로 숫자를 곱 하면 지수가 771 만큼 증가합니다.
따라서 지수는 (1075 + 771) = 1846 이고 이의 등가는 (11100110110)
이제 우리의 숫자는 양수이므로 부호 비트는 0 입니다.
수정 된 숫자는 다음과 같습니다.
부호 비트 + 지수 + 크기 (비트의 간단한 연결)
0111001101101011011000110111010101010011001010110010101101000011
m은 char 포인터로 변환되기 때문에 비트 패턴을 LSD에서 8의 청크로 나눕니다.
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
(16 진수에 해당하는 것은 :)
0x73 0x6B 0x63 0x75 0x53 0x2B 0x2B 0x43
다음과 같이 문자표에서 다음 중 하나입니다.
s k c u S + + C
이것이 만들어지면 m [1]은 0입니다. 이것은 NULL 문자를 의미합니다
이제 리틀 엔디안 머신 에서이 프로그램을 실행한다고 가정하면 (낮은 순서 비트는 낮은 주소에 저장 됨) 포인터 m은 가장 낮은 주소 비트를 가리키고 8의 척에서 비트를 차지하여 진행됩니다 (char로 캐스팅 된 유형으로 * ) 및 마지막 청크에서 00000000이 발생하면 printf ()가 중지됩니다 ...
그러나이 코드는 이식성이 없습니다.