C语言的坑

2022-05-29  本文已影响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!
上一篇下一篇

猜你喜欢

热点阅读