美文网首页
C语言的坑

C语言的坑

作者: 明翼 | 来源:发表于2022-05-29 09:11 被阅读0次

    一 前言

    C相对其他语言来说比较古老了,单从语法来说看似简单,其实也有不少坑的,稍有不慎就中招。

    二 有符号和无符号的坑

    2.1 有符号的移位操作

    上代码:

    #include <stdlib.h>
    #include <stdio.h>
    
    static void divide_by_two(int num)
    {
            while(num) {
               printf("%d\n",num);
               num = num >>1;
            }
    }
    
    int main(void)
    {
        int num ;
        scanf("%d",&num);
        divide_by_two(num);
        return 0;
    }
    

    本来想的操作是每次都将数字右移1位,即/2,下面是运行结果:

    miao@ubuntu-lab:~/c-test$ ./a.out
    8
    8
    4
    2
    1
    miao@ubuntu-lab:~/c-test$ ./a.out
    -8
    -8
    -4
    -2
    -1
    -1
    -1
    -1
    -1
    ....
    

    输入正整数是正常的,输入负值,会造成无线循环。
    原因

    在C的标准里面,有符号右移操作在C99未定义的,在gcc中实现为补上符号位,调试如下:

    (gdb) n
    -8
    16          divide_by_two(num);
    (gdb) s
    divide_by_two (num=-8) at test_sign.c:6
    6               while(num) {
    (gdb) n
    7                  printf("%d\n",num);
    (gdb) n
    -8
    8                  num = num >>1;
    (gdb) x /4tb &num
    0x7fffffffe4dc: 11111000        11111111        11111111        11111111
    (gdb) n
    6               while(num) {
    (gdb) x /4tb &num
    0x7fffffffe4dc: 11111100        11111111        11111111        11111111
    (gdb) n
    7                  printf("%d\n",num);
    (gdb) n
    -4
    8                  num = num >>1;
    (gdb) n
    6               while(num) {
    (gdb) x /4tb &num
    0x7fffffffe4dc: 11111110        11111111        11111111        11111111
    (gdb) n
    7                  printf("%d\n",num);
    (gdb) n
    -2
    8                  num = num >>1;
    (gdb) x /4tb &num
    0x7fffffffe4dc: 11111110        11111111        11111111        11111111
    (gdb) n
    6               while(num) {
    (gdb) n
    7                  printf("%d\n",num);
    (gdb) n
    -1
    8                  num = num >>1;
    (gdb) n
    6               while(num) {
    (gdb) x
    0x7fffffffe4e0: 00000000        11100101        11111111        11111111
    (gdb) x /4tb &num
    0x7fffffffe4dc: 11111111        11111111        11111111        11111111
    (gdb) n
    7                  printf("%d\n",num);
    (gdb) n
    -1
    8                  num = num >>1;
    (gdb) n
    6               while(num) {
    (gdb) x /4tb &num
    0x7fffffffe4dc: 11111111        11111111        11111111        11111111
    

    最终所有的位都变成 1了,从而导致死循环的产生,即因为-1 右移一位还是-1 而不是0.

    2.2 有符号和无符号整数比较

    代码如下:

    
    #define PRINT_COMPARE_RESULT(a, b) \
        if (a > b) { \
            printf( #a " > " #b "\n"); \
        } \
        else if (a < b) { \
            printf( #a " < " #b "\n"); \
        } \
        else { \
            printf( #a " = " #b "\n" ); \
        }
    
    int main()
    {
        int a = -1;
        unsigned short b = 2;
        unsigned int c = 2;
    
        short e = -1;
        unsigned short f = 1;
        PRINT_COMPARE_RESULT(a,b);
        PRINT_COMPARE_RESULT(a,c);
        PRINT_COMPARE_RESULT(e,f);
        return 0;
    }
    

    输出结果:

    root@ubuntu-lab:/home/miao/c-test# ./a.out
    a < b
    a > c
    e < f
    

    比较规则:

    1. 如果整数是可以用int的范围涵盖的,则转成int比较;
    2. 如果整数的范围无法用int涵盖,则转成unsigned int 比较。

    来看下:

    1. int 的a 和unsigned short、显然是可以用int涵盖范围的,所以a<b;
    2. 对于a和c 比较,由于c的范围超出了int,则转成unsigned int比较,则a符号位为1,所以是个很大的整数,从而a>c;
    3. 对于e和f 都可以用int的范围涵盖,所以都转成int比较,所以e<f.

    2.3 有符号和无符号的移位

    代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    
    
    int main ()
    {
        int a = 0x80000000;
        unsigned int b = 0x80000000;
        a = a >> 1;
        b = b >> 1;
        printf("a right shift value is 0x%X\n", a );
        printf("b right shift value is 0x%X\n", b );
        return 0;
    }
    

    输出:

       0x000055555555514e <+5>:     mov    %rsp,%rbp
       0x0000555555555151 <+8>:     sub    $0x10,%rsp
       0x0000555555555155 <+12>:    movl   $0x80000000,-0x8(%rbp)
       0x000055555555515c <+19>:    movl   $0x80000000,-0x4(%rbp)
    => 0x0000555555555163 <+26>:    sarl   -0x8(%rbp)
       0x0000555555555166 <+29>:    shrl   -0x4(%rbp)
    

    sarl 是算术右移,用符号位补位。
    shrl 是逻辑右移,用0补位。

    三 使用memcmp比较结构体

    看代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef struct padding_type {
        short m1;
        int m2;
    } padding_type_t;
    
    int main()
    {
        padding_type_t a = {
            .m1 = 0,
            .m2 = 0,
        };
        padding_type_t b;
    
        memset(&b, 0, sizeof(b));
    
        if (0 == memcmp(&a, &b, sizeof(a))) {
            printf("Equal!\n");
        }
        else {
            printf("No equal!\n");
        }
        return 0;
    

    看代码,用memcap比较了两个结构体成员,看起来值是一样的,当时却不相等。
    原因是因为为了内存对齐,a成员中间有填充一个short,值随机,那么我们可以改动下:

        printf("sizeof %ld\n",sizeof(a));
    

    打印结果为8,所以是有了对齐,如何更改让其相等那,只要去掉对齐即可,在gcc下如下:

    typedef struct padding_type {
        short m1;
        int m2;
    }  __attribute__((packed)) padding_type_t;
    

    attribute((packed)) 即取消对齐,打印出来结果如下:

    root@ubuntu-lab:/home/miao/c-test# ./a.out
    sizeof 6
    Equal!
    

    相关文章

      网友评论

          本文标题:C语言的坑

          本文链接:https://www.haomeiwen.com/subject/gwoiprtx.html