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
比较规则:
- 如果整数是可以用int的范围涵盖的,则转成int比较;
- 如果整数的范围无法用int涵盖,则转成unsigned int 比较。
来看下:
- int 的a 和unsigned short、显然是可以用int涵盖范围的,所以a<b;
- 对于a和c 比较,由于c的范围超出了int,则转成unsigned int比较,则a符号位为1,所以是个很大的整数,从而a>c;
- 对于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!