C结构体中定义长度为0的数组的意义

博客探讨了在C语言结构体中定义长度为0的数组在动态内存分配器中的作用。这样做允许结构体的大小根据需要变化,以适应不同大小的内存块。通过这种方式,可以避免额外的指针分配和释放操作,从而提高性能。同时,使用数组而非指针能减少缓存失效,因为内存是连续分配的。总结了使用0长度数组相对于直接使用指针的三个优势:节省内存、减少缓存失效和简化内存管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C结构体中定义长度为0的数组的意义

背景

写这篇博客的起因是在B站看到了一个视频,于是突然想起我之前写的一个动态内存分配器中也使用了类似的方法,于是写篇博客记录一下。

在动态内存分配器中定义了如下的一个结构体:

typedef uint64_t sf_header;
typedef sf_header sf_footer;
/*
 * Structure of a block.
 * The first field of this structure is actually the footer of the *previous* block.
 */
typedef struct sf_block {
    sf_footer prev_footer;  // NOTE: This actually belongs to the *previous* block.
    sf_header header;       // This is where the current block really starts.
    union {
        /* A free block contains links to other blocks in a free list. */
        struct {
            struct sf_block *next;
            struct sf_block *prev;
        } links;
        /* An allocated block contains a payload (aligned), starting here. */
        char payload[0];   // Length varies according to block size.
    } body;
} sf_block;

可以看到,在结构体 sf_block 中定义了一个联合体 body,之所以要定义为联合体而不是直接定义为 char payload[0];,是因为该动态内存分配器需要支持显式空闲链表(双向链表)来提升性能。问题是,联合体 body 中为什么要定义一个长度为0的数组呢?

解释

在实际使用中,动态内存分配器中块的大小是未知的,而结构体类型在定义后它的大小就确定了,为了能够使用结构体类型来描述这种大小未知的块,可以在结构体中定义一个长度为0的数组,为了进一步说明,看一个例子。

// 申请大小为1024个字节的动态内存,注意这个大小是指块中的负载大小,而不包括头部和脚部
sf_block* blk = (sf_block*) sbrk(sizeof(sf_footer) + sizeof(sf_header) + 1024);
// 访问这1024个字节
// blk->body.payload[0],访问第一个字节
// blk->body.payload[1],访问第二个字节
// blk->body.payload[2],访问第三个字节
// 负载的首地址为 blk->body.payload 或者 &blk->body

那为什么要使用 char payload[0];,而不是直接在结构体中定义一个 payload 的指针呢?使用 char payload[0];至少有以下三点好处

  1. 省了一个指针的内存占用(在我的例子中由于需要支持显式空闲链表而并没有体现这个好处)
  2. 因为是结构体,内存是分配在一起的而不是分开的,减少cache失效
  3. 分配和释放内存是很消耗性能的操作,如果用指针将需要两次分配两次释放,而这个只需要一次
### C语言结构体定义支持中文字符的字符数组 在C语言中,为了使结构体能够存储中文字符(无论是通过多字节编码还是Unicode),需要考虑所使用的编码方式。对于多字节字符集如GBK,在`struct`内声明适当大小的字符数组即可满足需求[^2]。 然而当目标转向更广泛的国际化应用时,则应采用宽字符类型`wchar_t`配合相应的Unicode编码方案,比如UTF-16或UCS-4。下面给出两种实现方法: #### 方法一:使用固定长度的多字节字符数组 (适用于GBK) ```c #include <stdio.h> // 定义一个简单的联系人类别 typedef struct { char name[7]; // 考虑到最常见情况下的姓氏加名字组合不会超过六个汉字加上终止符'\0' } Contact; int main(){ Contact person; // 假设当前环境设定为GBK, 此处仅作示意 strcpy(person.name,"张三"); } ``` 此法简单直接但灵活性较差,并且依赖于具体的本地化设置。 #### 方法二:利用可变宽度字符(`wchar_t`)与动态分配内存 (推荐用于处理Unicode) ```c #include <locale.h> #include <stdlib.h> #include <string.h> #include <wchar.h> #define MAX_NAME_LENGTH 30 typedef struct { wchar_t *name; // 动态分配空间以适应不同长度的名字 } UnicodeContact; void init_unicode_contact(UnicodeContact* contact){ setlocale(LC_ALL, ""); // 设置区域选项以便正确显示非ASCII字符 contact->name = (wchar_t*)malloc((MAX_NAME_LENGTH + 1)*sizeof(wchar_t)); } void free_unicode_contact(UnicodeContact* contact){ if(contact != NULL && contact->name != NULL){ free(contact->name); } } int main(){ UnicodeContact person; init_unicode_contact(&person); swprintf(person.name,L"%ls",L"李四"); wprintf(L"%ls\n", person.name); free_unicode_contact(&person); } ``` 上述代码片段展示了如何创建一个能容纳较长姓名字符串并兼容多种语言文字的结构体成员变量。这里采用了`wchar_t`类型的指针以及配套的标准库函数来进行操作,从而更好地应对复杂的文本数据处理场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值