c 的陷阱

你说的曾经没有我的故事 提交于 2020-03-12 22:22:13

c语言算是非常古老了,像瑞士军刀灵活却也很容易伤到自己,即使是多年的老杆子,以致于市面上都有一本经典的C的书叫《C陷阱与缺陷》的书。
image.png

这个文章总结下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 原因:参数从右到左处理。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!