c 的陷阱
c语言算是非常古老了,像瑞士军刀灵活却也很容易伤到自己,即使是多年的老杆子,以致于市面上都有一本经典的C的书叫《C陷阱与缺陷》的书。
这个文章总结下c中常见的陷阱,可能在日常工作或面试题目中遇到。
1. sizeof 陷阱
sizeof 它是一个编译时运算符而非函数,用于判断变量或数据类型的字节大小。
比较常见的用法:
int arr[] = { 1, 2, 3 };
for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
printf("%d,", arr[i]);
}
sizeof(arr)的是整个数组的占字节数大小,除int占字节大小就是整个数组的大小了,但是如果不小心这样用了:
void clear(char array[])
{
int i;
for (i = 0; i < sizeof(array) / sizeof(array[0]); i++)
{
array[i] = 0x00;
}
}
int main(void)
{
char arr[20];
clear(arr);
}
问题: 这段代码的问题,在于clear中传递的是指针,这时候sizeof(char*) 一般为4,sizeof(array[0]),造成了结果是只对数组的前四个变量赋值为0,其他的没有赋值!
2. 小心无符号类型
先看段代码:
#include <stdio.h>
int main()
{
if (-1L < 1U) {
printf("true");
} else {
printf("false");
}
return 0;
}
问题: 结果为false,原因,有符号和无符号比较的时候,将有符号转成无符号再比较。
还有一些看上去很傻的代码: 无符号char 最大255, 永远大于等于0,所以下面循环为死循环:a. unsigned char i; b. unsigned char i; for(i=0;i<256;i++) {… } for(i=10;i>=0;i--) { … }
3. volatile
volatile 标识变量是容易变化的,每次读取的时候不能从寄存器读取,需要从内存重新加载,多用在随时变化的变量,比如两个线程程序中,一个程序通过更改一个volatile类型的flag,另外一个线程判断这个flag为真还是假来决定是否继续执行。用volatile声明的变量编译器不会进行优化。
如果在一个a.h头文件中定义:
volatile unsigned int a;
在b.h 中引用:
extern unsigned int a;
编译器只会告警,但是运行的时候会有问题,隐藏的大Bug。
4. 局部变量
返回局部变量指针:
char * GetData(void)
{
char buffer[100]; //局部数组
…
return buffer;
}
说明: 很经典错误,buffer分配在栈上,函数调用结束后会释放。
5. 小心类型自动转换和提升
程序中,short,char,int,枚举,位段变量用在需要int的函数时候,自动转成int类型。
如果int可以完整的表示源类型的所有值,那么该源类型的值就转换为int, 否则转换为unsigned int,这被称为整体提升。——《C专家编程》
unsigned char -> int char ->int
比如:
char a = 'a', b = 'b'; printf("%d\n", sizeof(a + b)); printf("%d\n", sizeof(a));
说明: 输出的结果为:
4
1
当表达式使用到值的时候,就会进行进行类型提升。
6. free释放注意
free释放的传入的指针,必须是malloc申请的,在程序中,如果发生了计算,偏移了原始指针就会有问题:
#include<stdio.h>
int main(int argc, char *argv[])
{
char *ptr = (char*)malloc(10);
if(NULL == ptr)
{
printf("\n Malloc failed \n");
return -1;
}
else if(argc == 1)
{
printf("\n Usage \n");
}
else
{
memset(ptr, 0, 10);
strncpy(ptr, argv[1], 9);
while(*ptr != 'z')
{
if(*ptr == '')
break;
else
ptr++;
}
if(*ptr == 'z')
{
printf("\n String contains 'z'\n");
// Do some more processing
}
free(ptr);
}
return 0;
}
当传入的字符串第一个字符不是z的时候,循环会使ptr发生变化,再free的时候,会奔溃。
7. _exit退出
看下下面代码func为何未调用:
#include<stdio.h>
void func(void)
{
printf("\n Cleanup function called \n");
return;
}
int main(void)
{
int i = 0;
atexit(func);
for(;i<0xffffff;i++);
_exit(0);
}
说明: _exit不会调用atexit注册的退出函数,需要调用需要return或exit(0)。
8 参数处理顺序
#include<stdio.h>
int main(void)
{
int a = 10, b = 20, c = 30;
printf("\n %d..%d..%d \n", a+b+c, (b = b*2), (c = c*2));
return 0;
}
说明: 输出为:110..40..60 原因:参数从右到左处理。
作者:明翼(XGogo)
-------------
公众号:TSparks
微信:shinelife
扫描关注我的微信公众号感谢
-------------