你应该知道的C语言内存节省之法

码哥比特课程 2024-04-03 03:16:14

不论身在互联网大厂与否,很多C语言开发者们在项目中都会遇到一种情况:很多结构体中都会用到位变量作为一个开关标识。例如下面这个例子,

typedef struct connection_s { int sockfd; chain_t *recv_chain_head; chain_t *recv_chain_tail; chain_t *send_chain_head; chain_t *send_chain_tail; struct connection_s *next; unsigned int closed:1;} connection_t;

假设我们是在一台32位Linux操作系统中编译连接运行的。那么这个结构体占了多大空间呢?

这个问题涉及到两方面知识点:

位变量结构体对齐规则

位变量:就是closed的写法,即在变量名后跟:n,n位占用的位数。换言之,虽然closed声明位unsigned int(32位),但它只用到了1位,额外31位和它无关,对它的操作也影响不到额外31位。

结构体对齐:相信很多人都知道,编译器默认情况下在32位系统中,结构体是4字节对齐的,那么上面这个结构体的大小自然是:

4字节(sockfd)+ 4字节(recv_chain_head)+ 4字节(recv_chain_tail)+ 4字节(send_chain_head)+ 4字节(send_chain_tail)+ 4字节(next)+ 4字节(closed)

这里可以看到,尽管closed只占用一位,但是编译器依旧会向4字节对齐,因此即便31位未用到,也要算作开销。

在我们常见的高并发网络中,一个程序动辄支持上万链接,如果每一个链接多占31位,那么10000个链接就多占了38750字节,约为近38KB。这点开销对于高大上的服务器而言九牛一毛,但是如果是一些对内存占用量非常敏感的项目呢,或者项目要支持的是10万级百万级链接且一个程序中有如此状况的结构体也有很多,那么这么来看,会有很大一批内存被闲置浪费了。

是否还有办法压缩内存呢?

或许有人会提出修改默认对齐字节数,但这绝对不是一个好主意,因为CPU对奇地址内存读取会占用两个总线周期,而偶地址只需要一个。如果改为1字节对齐,那么就会存在有的变量的地址是奇地址,这会影响程序执行效率,绝非专业人士所愿。

下面介绍一种极客方法。

之前我们提到过,32位下是4字节对齐的,那么其实我们的结构体的地址(例如next指针指向的下一个connnection结构的地址)的低位最后两比特就一定为0。

既然有常为0的比特位,我们何不利用起来,针对上例,我们可以去掉closed变量,此时代码形如:

typedef struct connection_s { int sockfd; chain_t *recv_chain_head; chain_t *recv_chain_tail; chain_t *send_chain_head; chain_t *send_chain_tail; struct connection_s *next;} connection_t;

似乎我们缺少了一个位变量,无法完成closed标记了。但next后两位常年为0,我们可以利用其最后一位来替代closed位变量,做法形如:

connection->next |= 0x1;

而在以后如有需求遍历整个链的时候,我们可以如下做:

connection_t *next, *c = connection_head; //假设是connection_head全局变量while (c != NULL) { next = c->next & 0xfffffffc; //一些操作 c = next;}

这里看似我们是利用额外的位运算来取代了位变量所带来的对齐开销,但是通常情况下,由于位运算单指令即可完成且指令复杂度极低,因此运算效率也是非常高的,是非常划算的。

喜欢的小伙伴可以关注或私信码哥,后续码哥也会继续推出相关文章,谢谢观看!

0 阅读:0

码哥比特课程

简介:感谢大家的关注