網上流傳一段代碼聲稱能打印「2+2=5」,實則利用字符指針算術與棧上變量佈局的巧合。本文深度剖析其原理,並探討編譯器差異(GCC vs Clang)對棧佈局的影響。
int main() {
char __func_version__[] = "5"; // 目標字符串
char b[] = "2", a = 2; // a=2, b指向"2"
printf("%d + %s = %s\n", a, b, a + b); // a+b 做指針加法!
return 0;
}
a+b中a隱式轉為int,b退化為char*,結果是b + 2,即指向b後第3個字節。若棧佈局恰好使該地址落在__func_version__的'5'上,便打印出「5」。
#include <stdio.h>
int main() {
char __func_version__[] = "5";
char b[] = "2", a = 2;
printf("%p %p %p\n", __func_version__, b, &a);
printf("%d + %s = %s\n", a, b, a + b);
return 0;
}
輸出:
0x7fff504fa920 0x7fff504fa930 0x7fff504fa910 2 + 2 = OP?
__func_version__地址低於b,b+2指向更高地址,無法觸及目標,打印亂碼。
#pragma pack(1))#pragma pack(1)
typedef struct { char *b; char a; char *version; } foo;
void print_5_by_struct() {
foo f = {"2", 2, "5"};
printf("%d + %s = %s\n", f.a, f.b, f.a + f.b); // 穩定輸出 2 + 2 = 5
}
#pragma pack(1)消除填充字節,確保成員緊湊排列,f.b + 2精確落在f.version上。
同樣代碼在 clang -O3 下:
0x7fff57925936 0x7fff57925934 0x7fff57925933 2 + 2 = 5
Clang 將變量按聲明順序由高地址向低地址分配,恰好滿足條件。
ptr + n 移動 n * sizeof(*ptr) 字節。int 當指針偏移屬於未定義行為,生產代碼嚴禁此類技巧。%p觀察地址,理解內存模型是C程序員必修課。學C光看書不夠,必須「學以致用」,動手實驗內存佈局、編譯器行為,才能真正掌握語言本質。