Postgresql作为C语言开发的代码,其中大量的运用了一些宏的操作。
因此理解这些宏很重要,然而有时候这些宏总让人很费解。
作为一个经常翻翻postgresql源码的小白,在这里做一个记录吧,方便自己查看。
1. #define offsetof(type, field) ((long) &((type *)0)->field)
开始不理解为什么可以这样使用:(type *)0)->field,后来发现如果这样写,运行的时候程序会崩溃,但调用这个宏的时候为什么不会?
后来在网上找到了答案:ANSI C标准允许值为0的常量被强制转换成任何一种类型的指针,
并且转换结果是一个NULL指针,因此((type *)0)的结果就是一个类型为type *的NULL指针。
如果利用这个NULL指针来访问type的成员当然是非法的,
但&( ((type *)0)->field )的意图仅仅是计算field字段的地址。
聪明的编译器根本就不生成访问type的代码,
而仅仅是根据type的内存布局和结构体实例首址在编译期计算这个(常量)地址,
这样就完全避免了通过NULL指针访问内存的问题。
又因为首址为0,所以这个地址的值就是字段相对于结构体基址的偏移。
以上方法避免了实例化一个type对象,并且求值在编译期进行,没有运行期负担
参考文档:
2.各种 do { statement;} while (0) 结构
这种宏的用途是什么?有什么好处?
Google的Robert Love(先前从事Linux内核开发)给我们解答如下:do{...}while(0)在C中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。
例如:
#define foo(x) bar(x); baz(x)
然后你可能这样调用:
foo(wolf);
这将被宏扩展为:
bar(wolf); baz(wolf);
这的确是我们期望的正确输出。下面看看如果我们这样调用:
if (!feral) foo(wolf);
那么扩展后可能就不是你所期望的结果。上面语句将扩展为:
if (!feral) bar(wolf);baz(wolf);
显而易见,这是错误的,也是大家经常易犯的错误之一。
几乎在所有的情况下,期望写多语句宏来达到正确的结果是不可能的。你不能让宏像函数一样行为——在没有do/while(0)的情况下。
如果我们使用do{...}while(0)来重新定义宏,即:
#define foo(x) do { bar(x); baz(x); } while (0)
现在,该语句功能上等价于前者,do能确保大括号里的逻辑能被执行,而while(0)能确保该逻辑只被执行一次,即与没有循环时一样。
对于上面的if语句,将会被扩展为:
if (!feral) do { bar(wolf); baz(wolf); } while (0);
从语义上讲,它与下面的语句是等价的:
if (!feral) { bar(wolf); baz(wolf);}
这里你可能感到迷惑不解了,为什么不用大括号直接把宏包围起来呢?为什么非得使用do/while(0)逻辑呢?
例如,我们用大括号来定义宏如下:
#define foo(x) { bar(x); baz(x); }
这对于上面举的if语句的确能被正确扩展,但是如果我们有下面的语句调用呢:
if (!feral) foo(wolf);else bin(wolf);
宏扩展后将变成:
if (!feral) { bar(wolf); baz(wolf);};else bin(wolf);
大家可以看出,这就有语法错误了。
总结:Linux和其它代码库里的宏都用do/while(0)来包围执行逻辑,因为它能确保宏的行为总是相同的,而不管在调用代码中使用了多少分号和大括号。
参考文档:
3.#define MAXALIGN(LEN)
这种宏定义的作用是:为结构体申请空间时要考虑到字节对齐。导致申请的内存要比声明的变量总长度要大一些。
直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则:(在没有#pragma pack宏的情况下,务必看完最后一行)1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
ps:Vc,Vs等编译器默认是#pragma pack(8),所以测试我们的规则会正常;注意gcc默认是#pragma pack(4),并且gcc只支持1,2,4对齐。套用三原则里计算的对齐值是不能大于#pragma pack指定的n值。
参考文档:
4.#define DLIST_STATIC_INIT(name) { {&(name).head, &(name).head}}
这是用法:
static dlist_head BackendList = DLIST_STATIC_INIT(BackendList);
其中,dlist_head 的定义是:
typedef struct dlist_head{ dlist_node head;} dlist_head;struct dlist_node{ dlist_node *prev; dlist_node *next;};
那么,这个宏其实就是循环链表的定义+初始化,代码功能等价于:
struct dlist_head BackendList ; BackendList.prev=&(BackendList).head; BackendList.next=&(BackendList).head;
参考文档:
5.#SLIST_STATIC_INIT(name) { {NULL}}
struct slist_node{ slist_node *next;};typedef struct slist_head{ slist_node head;} slist_head;
其实这和上面的双向链表相对应,这是单向链表的定义+初始化,代码功能等价于:
slist_head name;name.next = NULL;
6.类似以下的宏系列
#define newNode(size, tag) \( \ AssertMacro((size) >= sizeof(Node)), /* need the tag, at least */ \ newNodeMacroHolder = (Node *) palloc0fast(size), \ newNodeMacroHolder->type = (tag), \ newNodeMacroHolder \)
刚开始看的时候迷迷糊糊,其实这就是个逗号表达式,在()内部的语句依次执行,最后返回最后一个表达式的值。
这类宏常常作为初始化一个复杂的结构时候使用,在逗号表达式内部依次对特定的结构进行初始化,将要返回的结构作为逗号表达式的最后一个式子。
未完待续......