C语言基础与陷阱:16道经典趣味测试题解析
本文整理自 C-Language Tests 网站的一套经典测试题,包含16道涉及指针、内存管理、宏定义及复杂语法结构的题目。这些题目能直观反映对C语言底层机制的理解深度,适合用于技术人员筛选或自我知识盲区排查。
核心原理与运行结果
第一题:setjmp/longjmp 异常处理机制
题目代码:
#include <setjmp.h>
static jmp_buf buf;
int main(void) {
volatile int b = 3;
if (setjmp(buf) != 0) {
printf("%d\n", b);
exit(0);
}
b = 5;
longjmp(buf, 1);
}
解析:输出结果为 5。setjmp首次调用返回0并记录环境状态;longjmp恢复该环境后,setjmp将返回非0值(此处为传入的1)。关键点在于变量 b 必须声明为 volatile,否则编译器优化可能不会重新读取栈中已修改的值,导致结果仍为初始值3。
第二题:结构体指针解引用
题目代码:
struct node { int a; int b; int c; };
struct node s = { 3, 5, 6 };
struct node *pt = &s;
printf("%d\n", *(int*)pt);
解析:由于 s 的第一个成员是 a(值为3),强制转换指针后解引用得到的是内存起始位置的4字节数据,即 3。若改为 char, char, short, int 且无填充的对齐结构,计算结果取决于具体编译器的字节序和对齐策略,通常第一个 int 会由前两个 char 和 short 的低字节组成。
第三题:递归算法推演
题目代码:
int foo(int x, int n){
int val = 1;
if (n > 0) {
if (n % 2 == 1) val *= x;
val *= foo(x * x, n / 2);
}
return val;
}
解析:这是实现快速幂的经典逻辑。通过手动展开递归树可轻松得出结果。对于特定的输入(如 $n=5, x=2$),需根据二进制位决定乘积因子,本质上是利用分治思想降低时间复杂度至 O(log N)。
第四题:数组指针与增量运算
题目代码:
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int*)(&a + 1);
printf("%d %d\n", *(a + 1), *(ptr – 1));
解析:&a 的类型是指向整个数组的指针。加1操作跨越整个数组长度(5 * sizeof(int)),指向数组末尾之后。ptr - 1 按 int* 步长回退一个位置,恰好落在最后一个元素 5 上。因此输出为 2 5。
第五题:多维数组传参退化
题目代码:
void foo(int b[][3]) {
++b;
b[1][1] = 9;
}
// ... main中调用 foo(a) 后输出 a[2][1]
解析:函数参数 b[][3] 在传参时退化为二级指针。虽然语法上像二维数组,但 ++b 实际上跳过了原数组的第一行(3个 int)。此时 b[1] 指向原数组的第二行开头,b[1][1] 对应的是原始数组 a[2][1],所以输出会被修改为 9。
第六题:逗号表达式与赋值优先级
题目代码:
c = a, b;
d = (a, b);
解析:赋值运算符 = 优先级高于逗号运算符 ,。
第一行等价于 (c = a), b;,所以 c 得到 a 的值。
第二行括号内先执行逗号表达式 (a, b),其结果为 b 的值,再赋给 d。
第七题:数组指针步进
题目代码:
int (*ptr)[3] = a; // 指向含3个元素的数组
// ... printf(... (*ptr)[1] ...) ; ++ptr; printf(...)
解析:ptr 是数组指针,步进一次跳过一整行(3个整数)。第一次访问第一行的第2、3个元素(2, 3);第二次访问第二行的第2、3个元素(5, 6)。
第八题:函数返回值的风险
题目代码:
// f1: 返回局部变量地址(危险)
// f2: 使用未初始化指针写入(未定义行为)
// f3: malloc申请堆内存(安全)
解析:C语言函数返回指针时应避免返回栈上的局部变量地址,以免产生悬垂指针。唯一安全的做法是返回静态变量、全局变量或通过 malloc 申请的堆内存。
第九题:sizeof 的执行时机
题目代码:
j = sizeof(++i + ++i);
解析:sizeof 是编译期求值的操作符,它检查表达式的类型但不实际执行其中的运算。因此 i 的值保持为 3 不变,j 为该表达式类型的字节数(32位环境下通常为 4)。
第十题:函数指针数组
题目代码:
p[0] = f1; p[0](&a, b); // 修改了a的值
解析:展示了函数指针数组的用法。通过传入指针可以修改变量本身,而普通整型参数传递则是副本,无法反向修改实参。
第十一题:递归调用顺序与副作用
题目代码:
e(n-1); printf("%d ", n); e(n-1);
解析:此题考察递归树的前序遍历特性以及前置递减操作的即时生效。通过绘制调用栈并跟踪 n 的变化,可以得出最终的打印序列。
第十二题:typedef 指针解析
题目代码:
typedef int (*test)(float*, float*);
解析:这是一种常见的复杂声明技巧。阅读时应从标识符向外扩展:test 是一个名为 test 的类型,它代表“指向接受两个 float 指针并返回 int 的函数的指针”。
第十三题:指针偏移与下标
题目代码:
p = (buf + 1)[5]; // 等价于 buf[6]
解析:利用指针加减法,buf + 1 指向第二个元素,再加5索引指向第七个元素(下标为6)。这验证了 a[n] 等同于 *(a+n) 的本质。
第十四题:[-1] 运算符的应用
解析:(p += size)[k] 这种写法常用于缓冲区操作,它在更新指针的同时访问相对位置的数据。理解关键在于运算符的求值顺序和指针算术的基本定义。
第十五题:va_list 可变参数
题目代码:
while(i) { i &= i - 1; ++k; }
解析:i &= i - 1 是计算二进制中 1 个数的高效位运算(Brian Kernighan算法)。每次操作消除最低位的1,循环次数即为1的个数。
第十六题:静态局部变量的持久性
题目代码:
count = count + i;
解析:带有 static 关键字的变量存储在静态区,其生命周期贯穿整个程序运行。即使函数退出,其保留的值在下一次进入时依然存在,从而实现简单的累加效果。
总结
以上是对C语言常见考点及易错区的详细解析内容,更多相关的语言进阶技巧与性能优化资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













