c语言学的好吗?来看看你直觉

基础语法陷阱

  1. int a = 5; printf("%d", a++ + ++a); 输出什么?
    (未定义行为,结果不可预测)

  2. if (sizeof(int) > -1) printf("True"); else printf("False"); 结果?
    (输出False,sizeof返回size_t,与-1比较时发生类型转换)

  3. char str[] = "Hello"; str[5] = '!'; 是否合法?
    (越界访问,字符串结尾隐含\0,索引5已超出)

  4. int x = 0; if (x = 1) { printf("True"); } 输出什么?
    (输出True,=赋值操作符返回左值)

  5. int a = 1; printf("%d", a << 32); 结果?
    (未定义行为,移位超过类型宽度)

  6. #define SQUARE(x) x * xSQUARE(1+2) 的值?
    (展开为1+2*1+2=5,而非预期的9)

  7. int a = 1; switch(a) { case 1: case 2: printf("A"); default: printf("B"); } 输出?
    (输出AB,case穿透到default)

  8. int a = 3, b = 5; int c = a/*b; 是否合法?
    (语法错误,/*被解析为注释开始)

  9. float f = 0.1; if (f == 0.1) printf("Equal"); 是否成立?
    (不成立,浮点数精度问题,0.1默认是double)

  10. int a[3] = {1,2,3}; printf("%d", 2[a]); 输出?
    (输出3,数组下标可写作a[2]2[a]


指针与内存管理

  1. int *p = NULL; printf("%d", *p); 会发生什么?
    (未定义行为,可能段错误)

  2. int arr[5]; int *p = arr + 5; *p = 10; 是否合法?
    (越界访问,指针指向数组末尾后一位,非法)

  3. char *str = "Hello"; str[0] = 'h'; 是否合法?
    (未定义行为,字符串字面量只读)

  4. void free(int *p) { /* ... */ } 能否自定义free函数?
    (合法,但会覆盖标准库函数,导致未定义行为)

  5. int *p = malloc(sizeof(int)); free(p); free(p); 是否合法?
    (双重释放,未定义行为)

  6. int a = 10; int *p = &a; printf("%d", sizeof(p)); 输出?
    (输出指针大小,如4或8,与a类型无关)

  7. int arr[][3] = {1,2,3,4}; 实际维度是?
    (等效于int arr[2][3],自动填充0)

  8. int (*p)[3]int *p[3] 的区别?
    (前者是数组指针,后者是指针数组)

  9. void func(int arr[]) { sizeof(arr); } 返回什么?
    (返回指针大小,数组退化为指针)

  10. char *p = malloc(0); 是否合法?
    (合法,但返回的指针可能为NULL或不可用地址)


预处理器与宏

  1. #include "file.c" 是否合法?
    (合法但危险,可能导致重复定义)

  2. #define MIN(a,b) a < b ? a : bMIN(1, 2++) 展开后?
    (导致2++被求值两次)

  3. #if sizeof(int) == 4 是否合法?
    (不合法,sizeof不能在预处理期使用)

  4. #define STR(x) #xSTR(1 + 2) 的结果?
    (字符串化为"1 + 2",空格被保留)

  5. #define CAT(a,b) a##bCAT(1, CAT(2,3)) 展开结果?
    (展开为1CAT(2,3),宏不递归展开)


类型与转换

  1. char c = 255; printf("%d", c); 输出?
    (若char有符号,输出-1;无符号则255)

  2. int a = -1; unsigned int b = a; printf("%u", b); 输出?
    (输出UINT_MAX,二进制补码转换)

  3. float f = 1E39; 是否合法?
    (未定义行为,溢出)

  4. int a = 1; long b = 2; if (a - b > 0) ... 是否成立?
    a转换为long,结果-1,不成立)

  5. printf("%d\n", (short)32768); 输出?
    (溢出,结果取决于实现,可能是-32768)


函数与作用域

  1. void func() { static int count = 0; count++; } 多次调用后count的值?
    (每次调用累加,静态变量只初始化一次)

  2. int main() { return main(); } 能否运行?
    (无限递归,栈溢出)

  3. extern int a;extern int a = 0; 的区别?
    (后者是定义,可能导致链接错误)

  4. void func(int a, int a) { ... } 是否合法?
    (非法,参数名重复)

  5. int a; int a = 10; 是否合法?
    (合法,int a;是暂定定义)


未定义行为(UB)

  1. int i = 0; printf("%d %d", i++, i++); 输出?
    (未定义,参数求值顺序不确定)

  2. int a = 1; int *p = &a; printf("%d", *(p + 1)); 是否合法?
    (越界访问,UB)

  3. int a = 1; a = a++; 是否合法?
    (UB,同一变量多次修改无序列点)

  4. memcpy(&a, &a, sizeof(a)); 是否合法?
    (UB,源和目标内存区域重叠)

  5. int *p; printf("%d", *p); 是否合法?
    (UB,使用未初始化的指针)



地狱级反直觉问题

1. 空指针的诡异访问
int *p = NULL;
int size = sizeof(*p);  // 是否触发未定义行为?

解析sizeof(*p) 不会解引用空指针sizeof的操作数在编译期仅进行类型推导,不会实际计算表达式值。此代码合法且返回sizeof(int)


2. 数组的负数索引
int arr[5] = {1,2,3,4,5};
int *p = &arr[2];
printf("%d", p[-2]);  // 输出什么?

解析:输出1。指针允许负数索引,只要最终地址在数组范围内。p[-2]等价于*(p - 2),即arr[0]


3. 字符串拼接的魔法
printf("Hello " "World!\n");  // 输出?

解析:输出Hello World!。C语言在编译期自动拼接相邻字符串字面量,即使中间无+操作符。


4. 函数指针的变形术
void (*func)() = (void(*)())0xDEADBEEF;
func();  // 会发生什么?

解析:尝试跳转到绝对地址0xDEADBEEF执行代码。在嵌入式系统中可能合法,但通常触发未定义行为(如段错误)。


5. 循环中的浮点数陷阱
for (float f = 0.0; f != 1.0; f += 0.1) {
    printf("%f\n", f);
}

解析无限循环!浮点数精度问题导致f永远无法精确等于1.0(例如最后一步可能是0.999999)。


6. 结构体的“隐身”成员
struct { char a; int b; } s;
printf("%zu", sizeof(s));  // 输出?

解析:输出可能是8(32位系统)或更高。结构体内存对齐导致ab之间有填充字节(padding)。


7. 预处理器的“时间旅行”
#define A B
#define B A
int A = 10;  // 最终变量名是什么?

解析:变量名为A。宏展开在递归检测时停止,防止无限循环。


8. 位域的神秘截断
struct { unsigned int a:3; } s = {7};
s.a++;  // 现在s.a的值是?

解析s.a变为0!3位无符号位域最大值为7(111),加1后溢出为000


9. 逗号操作符的优先级
int x = (1, 2, 3);
int y = 1, 2, 3;  // 是否合法?

解析x被赋值为3(逗号表达式返回最后一个值),但第二行语法错误(逗号被解析为分隔变量声明)。


10. 未初始化的静态变量
static int a;
printf("%d", a);  // 输出什么?

解析:输出0。静态变量默认初始化为零值,与自动变量不同。


编译器魔法与UB的狂欢

11. 无限循环的消失
while (1) { /* 空循环 */ }  // 编译器会优化掉吗?

解析:C标准规定无限循环必须有可观察行为,否则编译器可删除此循环!添加printfvolatile变量可阻止优化。


12. 指针的整数转换
int *p = (int*)1000;
p++;  // p的值现在是多少?

解析p指向1000 + sizeof(int)。指针算术以指向类型的大小为单位(如sizeof(int)=4则结果为1004)。


13. 除零的“安全”操作
int a = 0, b = 1;
float c = b / a;  // 是否触发异常?

解析不触发异常,但结果为Infinity(浮点数除零在IEEE 754中定义),而整数除零直接导致UB。


14. 联合体的类型双胞胎
union { int a; float b; } u;
u.a = 123;
printf("%f", u.b);  // 输出有意义吗?

解析:输出取决于intfloat的二进制表示,可能打印出无意义的浮点数(如1.72e-43)。


15. 函数参数的求值顺序
int i = 0;
printf("%d %d", i++, i++);  // 输出什么?

解析未定义行为!C标准未规定函数参数的求值顺序(可能是0 01 0,取决于编译器)。


终极黑暗技巧

16. 修改字符串字面量
char *str = "hello";
str[0] = 'H';  // 是否合法?

解析未定义行为!字符串字面量存储在只读内存段,修改会导致段错误(但某些嵌入式系统可能允许)。


17. 数组的越界初始化
int arr[3] = {1,2,3,4};  // 是否合法?

解析编译错误!初始化列表长度超过数组大小。


18. 类型双关的别名冲突
float pi = 3.14;
int *ip = (int*)&pi;
printf("%d", *ip);  // 是否合法?

解析:严格别名规则下属于未定义行为(通过不同类型指针访问同一内存),但实践中可能输出pi的二进制整数表示。


19. 预处理器的字符串化魔法
#define STR(s) #s
printf("%s", STR(\n\t\\"));  // 输出什么?

解析:输出换行符\n、制表符\t、反斜杠\\和双引号\"(字符串化会自动转义特殊字符)。


20. 零长度数组的幽灵
struct S { int len; char data[0]; };
struct S *s = malloc(sizeof(*s) + 10);
s->data[5] = 'A';  // 是否合法?

解析合法(GCC扩展)。零长度数组常用于动态结构体尾部,但C99后应使用灵活数组成员(char data[];)。



极度反直觉案例(附解析)

1. 时间旅行的restrict关键字
int foo(int *restrict a, int *restrict b) {
    *a = 10;
    *b = 20;
    return *a; // 返回值是10还是20?
}

解析:编译器可能优化为直接返回10restrict承诺两个指针不重叠,因此假设*b的修改不影响*a。但若实际传入重叠指针,则是UB。


2. volatile的失效时刻
volatile int flag = 0;
while (!flag); // 其他线程可能修改flag

解析:即使flagvolatile循环仍可能被优化为无限空循环!某些编译器(如旧版GCC)在多线程场景下不保证volatile的可见性,需用原子操作或内存屏障。


3. 结构体赋值的“量子态”
struct S { int a; char b; };
struct S s1 = {1, 'A'};
struct S s2 = s1; // 是否复制填充字节?

解析可能不复制填充字节!结构体赋值按成员复制,但填充字节的值是未定义的(可能是原值或随机值)。用memcpy更安全。


4. switch的隐藏入口
int i = 2;
switch (i) {
    case 1: printf("A");
    case 2: printf("B");
    case 3: printf("C");
}

解析:输出B Ccase语句默认“穿透”(fall-through),需手动加break。但若i=5且无default,则整个switch静默跳过。


5. 数组名的“死亡转换”
int arr[5];
printf("%p %p", arr, &arr); // 输出是否相同?

解析:地址值相同,但类型不同arr类型是int*&arr类型是int(*)[5]arr+1前进sizeof(int)&arr+1前进sizeof(int)*5


6. 函数返回的“僵尸栈”
int *func() {
    int a = 42;
    return &a;
}
int *p = func();
printf("%d", *p); // 能否输出42?

解析:可能输出42(栈未覆盖),但属于UB!函数返回后栈帧失效,但编译器未擦除数据,导致“暂时可用”的幻觉。


7. const的虚假安全感
const int a = 10;
int *p = (int*)&a;
*p = 20;
printf("%d %d", a, *p); // 输出什么?

解析:可能是10 20!编译器可能将a优化为立即数,而*p修改的是内存中的副本。此行为属于UB,可能导致程序崩溃。


8. 预处理器的“毒药宏”
#define malloc(n) evil_malloc(n)
void *evil_malloc(size_t n); 
// 之后所有代码中的malloc都会被替换!

解析:包括标准库头文件中的malloc!在包含<stdlib.h>前定义此类宏,会导致标准库内部使用被篡改的malloc,引发灾难。


9. 浮点数的“静默降级”
float f = 0.1;
double d = 0.1;
if (f == d) printf("Equal");
else printf("Not Equal"); // 输出?

解析:输出Not Equal0.1floatdouble中的二进制表示不同,比较时float被提升为double,但精度损失导致不等。


10. _Generic的“类型幻觉”
#define type(x) _Generic((x), \
    int: "int", double: "double")
char s[] = "hello";
printf("%s", type(s)); // 输出什么?

解析编译错误_Generic的控制表达式(x)会推导数组类型char[6],但未在列表中处理,触发编译失败。


编译器优化的魔法与诅咒

11. 消失的变量
int func() {
    int a = 5;
    int b = a + 1;
    return b;
}
// 编译优化后可能变为直接返回6,变量a和b被删除
12. 循环的“时空折叠”
for (int i = 0; i < 1000; i++) {
    arr[i] = i * 2;
}
// 可能被向量化(SIMD指令)并行处理,而非顺序执行
13. 死代码的“复活”
if (0) { 
    printf("Hello"); // 死代码
}
// 某些调试模式下,死代码仍会被编译(保留符号表)

恶魔级代码(切勿在实际项目中使用!)

14. 自修改代码
void func() {
    char *p = (char*)func;
    p[0] = 0xC3; // 修改函数开头为RET指令(x86)
}
// 调用func()后将立即返回,后续调用也失效(UB)
15. 利用未定义行为崩溃
int a = 1;
int b = 0;
int c = a / b; // 触发UB
// 可能:程序崩溃、输出任意值、静默继续执行,甚至删除整个程序逻辑!
16. 内存撕裂的原子性
// 线程1:
long x = 0;
x = 0x123456789ABCDEF0; // 非原子写入(32位系统)
// 线程2:
printf("%lx", x); // 可能看到中间状态(如0x1234567800000000)

为什么这些更反直觉?

违反物理世界直觉:如自修改代码、死代码复活;
与高级抽象背道而驰:如restrict的优化假设、volatile的不可靠性;
编译器与硬件的共谋:如循环向量化、变量消除;
类型系统的“欺骗”:如_Generic的严格类型检查、const的假不可变性;
时间与空间的混沌:如栈帧失效后的数据残留、结构体填充的随机性。


黑洞级反直觉问题(附解析)

1. 数组下标的时间扭曲
int i = 1;
int arr[] = {10, 20, 30};
printf("%d", i[arr - 1]);  // 输出什么?

解析:输出20!等价于*(i + (arr - 1))*(1 + arr -1 )arr[0]下一个元素。实际行为是UB,但可能输出20。


2. 预处理器的"薛定谔宏"
#define M(a) a + 1
#define MM(a) M(a) * M(a)
printf("%d", MM(1));  // 输出什么?

解析:展开为1 + 1 * 1 + 13,而非预期的(1+1)*(1+1)=4。宏展开不自动添加括号。


3. 浮点数的"维度折叠"
float a = 1.0;
float b = 0.0;
float c = a / b;  // INFINITY
float d = c * 0;
printf("%f", d);  // 输出什么?

解析:输出nanINFINITY * 0在IEEE 754中属于未定义操作,生成NaN(Not a Number)。


4. 结构体的"平行宇宙"
struct A { int x; };
struct B { int x; };
struct A a = {1};
struct B *b = (struct B*)&a;
printf("%d", b->x);  // 是否合法?

解析合法但危险!C标准允许通过兼容类型的指针访问,但严格别名规则下属于UB。可能输出1或引发崩溃。


5. 指针的"量子隧穿"
int arr[5];
int *p = &arr[5];  // 指向数组末尾后的地址
printf("%d", p[-5]);  // 访问arr[0]吗?

解析:语法合法但属于UB!指针允许指向数组末尾后的地址,但解引用该地址非法。可能正常访问arr[0],也可能触发段错误。


编译器的时间魔法

6. 循环变量的"相对论"
for (int i = 0; i < 5; i++) {
    printf("%d", i);
    i = i % 3;  // 循环几次?
}

解析无限循环!每次迭代i被重置为0/1/2,永远无法达到5。但某些编译器可能优化掉冗余操作。


7. 函数的"时空折叠"
void func() {
    static int count = 0;
    if (count++ < 5) 
        func();
}
// 实际调用栈深度?

解析仅2层栈帧!编译器可能进行尾递归优化(TCO),将递归转换为循环。但C标准不强制要求TCO。


8. 位域的"概率云"
struct {
    unsigned a:3;
    unsigned :0;  // 零宽度位域
    unsigned b:5;
} s;
printf("%zu", sizeof(s));  // 大小?

解析:可能是4字节!零宽度位域强制对齐到下一个存储单元(通常为int宽度),导致a和b分开存储。


内存的混沌领域

9. malloc的"虚空造物"
int *p = malloc(0);
if (p != NULL) {
    *p = 42;  // 是否合法?
}

解析UB!malloc(0)可能返回NULL或独特指针,但解引用该指针必然越界。


10. 内存的"幽灵共振"
int *p = malloc(sizeof(int));
free(p);
int *q = malloc(sizeof(int));
if (p == q) {  // 可能成立吗?
    *p = 42;    // 会发生什么?
}

解析:可能成立!内存分配器可能重用相同地址。此时*p=42会修改*q指向的内存,属于UB但可能静默生效。


语法的时间悖论

11. 标签的"第四维跳跃"
goto label;
int x = 5;  // 跳过后是否初始化?
label:
printf("%d", x);

解析:输出未定义值!跳转到跨越变量初始化的标签属于UB,x可能包含随机值。


12. switch的"多世界诠释"
switch (1) {
    default: printf("A");
    case 1: printf("B");
    case 2: printf("C");
}

解析:输出BC!default不要求放在最后,case执行后继续穿透后续分支。


终极黑暗技巧

13. 标准库的"恶魔低语"
printf("%d\n", printf("Hello!"));  // 输出什么?

解析:先输出"Hello!",再输出6(printf返回写入的字符数)。但输出流顺序可能因缓冲策略变化。


14. 信号的"因果倒置"
#include <signal.h>
void handler(int sig) { /* 修改全局变量 */ }
int main() {
    signal(SIGINT, handler);
    while(1) {
        // 全局变量修改可能永远不会被看到!
    }
}

解析:编译器可能将全局变量缓存在寄存器中,导致信号处理程序的修改不可见。需声明变量为volatile


15. 预处理器的"克苏鲁召唤"
#define DEFINE_STRUCT(name) \
    struct name {           \
        int name;           \
        struct name* name;  \
    }
DEFINE_STRUCT(MyStruct);  // 是否合法?

解析合法!生成的结构体包含同名成员和指针:struct MyStruct { int MyStruct; struct MyStruct* MyStruct; }; 完全合法但极度危险。


为何这些是终极反直觉?

  1. 违反物理世界直觉:零宽度位域对齐、指针虚空解引用
  2. 挑战逻辑因果律:goto跳过初始化、递归的栈折叠
  3. 暴露计算机本质:内存重用、尾递归优化
  4. 类型系统的崩塌:结构体自指、同名成员
  5. 编译器的"读心术":变量缓存优化、死代码复活
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值