最近在准备分析Linux input子系统,发现内核代码里面有很多小技巧。特此记录下,如有不足之处,敬请指正。

一、前言

在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,比如说,我们要在结构体中存放一个名字,但是这个名字的长度是未知的。于是,我们就会采用以下两种方法来解决这个问题。
注:以下的代码都是在下面的平台进行验证测试的:

Linux version 2.6.32-38-generic (buildd@allspice) (gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5) ) #83-Ubuntu SMP Wed Jan 4 11:12:07 UTC 2012

二、为数据结构成员申请空间

碰到上面的情况,一般而言我们会这么做:首先在结构体中定义一个指针成员,然后为这个指针成员malloc()一定长度的空间,进而往该空间写入字符串。

#include <stdio.h>
#include <malloc.h>
#include <string.h>

struct node{
    int num;
    int data;
    char *name;   // 结构体成员定义为指针
};

char test_name[] = "testname";

int main(void)
{
    struct node *test_node;
    int lenght = strlen(test_name);
    test_node = (struct node*)malloc(sizeof(struct node));
    test_node->name = (char *)malloc(lenght);
    strncpy(test_node->name, test_name, lenght);

    printf("sizeof(struct node) = %d\n", sizeof(struct node));
    printf("num addr:%p\n", &(test_node->num));
    printf("data addr:%p\n", &(test_node->data));
    printf("name addr:%p\n", test_node->name);
    printf("name = %s\n", test_node->name);
    printf("name[0] = %c\n", test_node->name[0]);
    printf("name[%d] = %c\n", lenght-1, test_node->name[lenght-1]);

    free(test_node->name);
    free(test_node);

}

上述代码的执行结果为:

sizeof(struct node) = 16
num addr:0x10ba010
data addr:0x10ba014
name addr:0x10ba030
name = testname
name[0] = t
name[7] = e

从中,我们可以看出:
1、如果想要实例化test_node,第一次要为test_node申请大小为sizeof(struct node)的空间,第二次再为其成员name申请lenght大小的空间。
首先我们要调用两次malloc(),调用两次,意味着最后也要跟着free()两次;其次,如果第二次为name成员申请空间失败的话,需要返回回去将test_node free掉。这样工作量就上来了,而且容易导致malloc()free()没对应进而出现内存错误。
2、我们看到,sizeof(struct node)的大小为16,即4+4+8的大小。这个可以和下面的代码进行对比。
3、通过打印test_node三个成员的地址分别为:

num addr:0x10ba010
data addr:0x10ba014
name addr:0x10ba030

很明显,成员name的地址与成员numdata的地址并不是连续的。小内存的管理是非常困难的,如果用指针,这个name的部分就是小内存了,在系统内存在多了势必严重影响内存管理的性能。要是用空数组把结构体和实际数据缓冲区一次分配大块问题,就没有这个问题。

三、采用空数组

为了规避上述代码的问题,我们采用下面的方法来解决:

#include <stdio.h>
#include <malloc.h>
#include <string.h>

struct node{
    int num;
    int data;
    char name[0];  // 结构体最后的成员定义一个长度为[0]的数组
};

char test_name[] = "testname";

int main(void)
{
    struct node *test_node;
    int lenght = strlen(test_name);
    test_node = (struct node*)malloc(sizeof(struct node)+lenght);
    //strncpy((char *)test_node+1, test_name, lenght);
    strncpy(test_node->name, test_name, lenght);

    printf("sizeof(struct node) = %d\n", sizeof(struct node));
    printf("num addr:%p\n", &(test_node->num));
    printf("data addr:%p\n", &(test_node->data));
    printf("name addr:%p\n", test_node->name);
    printf("test_node+1 addr:%p\n", test_node+1);
    printf("name = %s\n", test_node->name);
    printf("name[0] = %c\n", test_node->name[0]);
    printf("name[%d] = %c\n", lenght-1, test_node->name[lenght-1]);

    free(test_node);

}

运行结果为:

sizeof(struct node) = 8
num addr:0x1574010
data addr:0x1574014
name addr:0x1574018
test_node+1 addr:0x1574018
name = testname
name[0] = t
name[7] = e

通过比较,我们不难看出:
1、此时,实例化test_node只调用一次malloc(),意味着最后也只要free()一次。为其申请了一个长度为sizeof(struct node)+lenght的空间,所申请到的空间是连续的,没有小内存的情况出现。减小了对内存管理性能的影响。
2、同样的,sizeof(struct node)为8,即4+4,因此我们可以知道这里的成员name并没有占用空间,即使占用一个指针大小的空间都没有,因此这种方式可以节省空间。
3、此时test_node三个成员的地址分别为:

num addr:0x1574010
data addr:0x1574014
name addr:0x1574018

可以明显的看到,这个地址是连续的。因为我们只malloc()一次。
4、并且,我们还知道test_node+1的地址和test_node->name的地址是一样的,说明两者指向同一块内存区域,这更进一步的验证了成员name并没有占用空间。

四、柔性数组成员

C99使用不完整类型实现柔性数组成员,在C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组(flexible array)成员(也叫伸缩性数组成员),但结构中的柔性数组成员前面必须至少一个其他成员。柔性数组成员允许结构中包含一个大小可变的数组。柔性数组成员只作为一个符号地址存在,而且必须是结构体的最后一个成员,sizeof 返回的这种结构大小不包括柔性数组的内存。柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

参考文献:
深入浅出C语言中的柔性数组
第八章 柔性数组成员
结构体中最后一个成员为[0]或[1]长度数组(柔性数组成员)的用法

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐