C编程的一些注意事项

bit field

位字段以结构体形式声明,结构体为每个字段声明标签,并定义宽度。例如:

struct BitFileds {
    unsigned int a  :2;
    unsigned int b  :4;
    unsigned int    :2;
    unsigned int c  :8;
};

其中,a、b、c分别占据2、4、8位,中间还有一个匿名字段,占2个位。按常规结构体定义该结构占据4个int大小,实际上它只占一个short大小。

需要注意的是整个struct的长度是“implementaion defined”,也就是说编译器决定。见标准 14882:2003,9.6 节第一款。

移位操作符

【注】移位之后补0,但有唯一例外:右移操作符对 int x=1«31 右移后补位1。

int x = 1<<31;          // 1000 0000 0000 0000 0000 0000 0000 0000
x >> 1;                 // 1100 0000 0000 0000 0000 0000 0000 0000
unsigned int y = 1<<31; // 1000 0000 0000 0000 0000 0000 0000 0000
y >> 1;                 // 0100 0000 0000 0000 0000 0000 0000 0000

说明:Java的移位操作符和C一样,unsigned类型右移补0;signed类型右移根据符号位:正数补0、负数补1。

位运算技巧:

  • 将int型变量a的第k位清0:a=a&~(1<<k)
  • 将int型变量a的第k位置1:a=a|(1<<k)

定义变长TLV

struct TLV {
    uint8_t tag;
    uint16_t len;
    char value[0];
} __attribute__((packed));

最后的__attribute__((packed))可以强制不对struct TLV进行字节对齐。

作用域

该loop只执行一遍:

do {
    ...
    continue;
} while(0);

指针

指针加减运算

指针的加减运算是和 指针类型相关 的。如果加减N,则实际指针移动长度为 N*sizeof(指针类型),绝对不是单纯的移动数量N。

如果想得到struct内成员之间的偏移量,可以使用 offsetof 宏。 同类型指针之间的差值使用 ptrdiff_t 类型。

声明函数指针

typedef int (*func)(void);

声明了一个指向 int test(void); 函数的函数指针。

声明指向数组的指针

int (*ptr)[10];

声明了一个指向 int num[10]; 数组的指针。

注意: 与 int **ptr 的区别,这声明了一个指向指针的指针。 与 int *ptr[10] 的区别,这是声明了一个指针数组。

int (*ptr)[10]; 这个定义也可以拆成两句:

typedef int t[10];
t *ptr;

其中,t代表由10个int组成的数组类型,ptr则是指向这种类型的指针。

数组

一维数组

int a[10];
int (*pa)[10] = &a;

这里定义了一个数组a, 一个指向数组的指针 pa*pa表示其指向的数组a,所以(*pa)[0]可以获取a[0]元素。

此外,&a[0] 类型为 int*,而 &a 类型为 int(*)[10],但它们指向的地址相同。

二维数组

int a[5][10];
int (*pa)[10] = &a[0];

pa[0]a[0]地址和类型都相同,指向类型为int[10]的一维数组。 可以把pa当作二维数组名来使用,而且paa更灵活,因为数组名不支持赋值、自增操作,而指针支持。比如,pa++可以使pa跳过二维数组的一行,指向a[1]

gdb查看数据类型

int a[5] = {1,2,3,4,5};
int* pi = &a+1;
printf("%d\n", *(pi-1));

执行打印 5,可以debug一下a的数据类型:

(gdb) l
1       int main()
2       {
3           int a[5];
4       }
(gdb) ptype &a
type = int (*)[5]
(gdb)