C语言的一些随笔
🔰 C语言常见问题
1. 指针和数组的区别
**** 请解释指针和数组的区别?
答:
// 数组
int arr[5] = {1, 2, 3, 4, 5};
// 指针
int *ptr = arr;// 主要区别:
// 1. 内存分配:数组在编译时分配固定内存,指针只存储地址
// 2. sizeof:sizeof(arr) = 20字节,sizeof(ptr) = 8字节(64位系统)
// 3. 可修改性:arr不可修改指向,ptr可以重新赋值
// 4. 初始化:数组必须在声明时确定大小,指针可以动态分配
2. malloc和free的使用
问 如何正确使用malloc和free?
答:
// 正确使用示例
int *ptr = (int*)malloc(10 * sizeof(int));
if (ptr == NULL) {printf("内存分配失败\n");return -1;
}
// 使用内存...
free(ptr);
ptr = NULL; // 避免悬空指针// 常见错误:
// 1. 忘记检查返回值
// 2. 内存泄漏(忘记free)
// 3. 重复释放
// 4. 释放后继续使用
3. 结构体和联合体的区别
问: struct和union有什么区别?
答:
struct Student {int id; // 4字节float score; // 4字节char name[20]; // 20字节
}; // 总大小:28字节(考虑对齐)union Data {int i; // 4字节float f; // 4字节char str[20]; // 20字节
}; // 总大小:20字节(最大成员的大小)// 区别:
// 1. 内存布局:struct各成员有独立内存,union共享内存
// 2. 同时访问:struct可以同时访问所有成员,union只能访问一个
// 3. 大小:struct是所有成员大小之和,union是最大成员的大小
4. const关键字的使用
问: const在不同位置的含义?
答:
// 1. 常量变量
const int a = 10; // a不可修改// 2. 指针相关
int x = 5, y = 10;
const int *p1 = &x; // 指向常量的指针,*p1不可修改,p1可修改
int * const p2 = &x; // 常量指针,*p2可修改,p2不可修改
const int * const p3 = &x; // 常量指针指向常量,都不可修改// 3. 函数参数
void func(const int *arr); // 函数内不能修改arr指向的内容
5. 函数指针的使用
问: 如何定义和使用函数指针?
答:
// 定义函数
int add(int a, int b) {return a + b;
}// 函数指针定义和使用
int (*func_ptr)(int, int) = add;
int result = func_ptr(3, 4); // result = 7// 函数指针数组
int (*operations[])(int, int) = {add, subtract, multiply};
result = operations[0](5, 3); // 调用add函数
🔥 C语言深入
1. 内存对齐原理
问: 解释内存对齐的原理和作用?
答:
struct Example {char c; // 1字节int i; // 4字节char d; // 1字节
};// 不对齐:1 + 4 + 1 = 6字节
// 实际大小:12字节(因为对齐)// 内存布局:
// [c][_][_][_][i][i][i][i][d][_][_][_]
// 1 2 3 4 5 6 7 8 9 10 11 12// 对齐规则:
// 1. 成员对齐:每个成员按自身大小对齐
// 2. 结构体对齐:整个结构体按最大成员大小对齐
// 3. 作用:提高CPU访问效率,避免总线错误
2. volatile关键字
问: volatile的作用和使用场景?
答:
// volatile告诉编译器该变量可能被意外改变
volatile int flag = 0;// 使用场景:
// 1. 硬件寄存器
volatile int *hardware_reg = (int*)0x12345678;// 2. 中断处理
volatile int interrupt_flag = 0;
void interrupt_handler() {interrupt_flag = 1; // 中断中修改
}// 3. 多线程共享变量
volatile int shared_data = 0;// 作用:防止编译器优化,确保每次都从内存读取
3. 函数调用约定
问: 不同的函数调用约定有什么区别?
答:
// 1. __cdecl(默认):调用者清理栈
int __cdecl func1(int a, int b);// 2. __stdcall:被调用者清理栈
int __stdcall func2(int a, int b);// 3. __fastcall:寄存器传参
int __fastcall func3(int a, int b);// 区别:
// 1. 参数传递顺序:从右到左
// 2. 栈清理责任:调用者 vs 被调用者
// 3. 寄存器使用:不同约定使用不同寄存器
// 4. 性能影响:寄存器传参更快
4. 位域和字节序
问: 位域的使用和字节序问题?
答:
// 位域定义
struct BitField {unsigned int a : 3; // 3位unsigned int b : 5; // 5位unsigned int c : 8; // 8位
};// 字节序检测
union {int i;char c[4];
} test = {0x12345678};// 大端序:c[0] = 0x12, c[1] = 0x34, c[2] = 0x56, c[3] = 0x78
// 小端序:c[0] = 0x78, c[1] = 0x56, c[2] = 0x34, c[3] = 0x12int is_little_endian() {return test.c[0] == 0x78;
}
5. 递归和栈溢出
问: 如何避免递归导致的栈溢出?
答:
// 危险的递归(可能栈溢出)
int factorial_bad(int n) {if (n <= 1) return 1;return n * factorial_bad(n - 1);
}// 尾递归优化
int factorial_tail(int n, int acc) {if (n <= 1) return acc;return factorial_tail(n - 1, n * acc);
}// 迭代版本(推荐)
int factorial_iter(int n) {int result = 1;for (int i = 1; i <= n; i++) {result *= i;}return result;
}// 避免栈溢出的方法:
// 1. 设置递归深度限制
// 2. 使用尾递归
// 3. 转换为迭代
// 4. 使用动态分配的栈
⚠️ C语言易错
1. 数组作为函数参数
问: 下面代码的输出是什么?
void func(int arr[])