C语言结构体中相关问题

少于 1 分钟读完

记录一些C语言相关的问题。

弹性数组

最近看redis中的数组sds设计,结构如下:

// sdshdr 结构
struct sdshdr {

// buf 已占用长度
    int len;

// buf 剩余可用长度
    int free;

// 实际保存字符串数据的地方
// 利用c99(C99 specification 6.7.2.1.16)中引入的 flexible array member,通过buf来引用sdshdr后面的地址,
// 详情google "flexible array member"
    char buf[];
};

结构体sdshdr的实际长度为8,也就是说buf不占用内存空间,它表示的是一个地址的概念。我们定义一个struct sdshdr的变量s,在gdb中可以看见该变量的相关信息。

struct sdshdr

在这里,buf为弹性数组,关于弹性数组,我们从先从使用说起。

int main() {
    int this_len = 10;
    struct sdshdr *s = (struct sdshdr*)malloc(sizeof(struct sdshdr) + this_len + 1);
    s->len = this_len;
    s->free = 0;
    memset(s->buf, 'a', this_len);
    s->buf[this_len] = '\0';

    return 0;
}

从上面可以看出,我们给结构体分配了一个连续的内存。这样有两个好处:

  1. 方便内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
  2. 这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。

用gdb的x命令查看内存地址可以发现它们连续存储。

x/19b

结构体成员变量地址问题

这个问题来自于coolshell中的一篇文章,对理解c语言中的地址和指针很有帮助。

#include <stdio.h>

struct str {
    int len;
    char s[0];
};

struct foo {
    sturct str *a;
};

int main(int argc, char **argv) {
    struct foo f = {0};
    if(f.a->s) {
        printf(f.a->s);
    }
    return 0;
}

编译和运行程序,会在printf(f.a->s)这一行出现段错误。而不会在if(f.a->s)这一行挂掉,如果把语句printf(f.a->s)改成printf(“%x\n”, f.a->s),程序也可以顺利通过,输出结果4。这是为什么呢?首先需要知道s是数组名,在c语言中,数组名表示数组的起始地址,它是一个地址,所以if(f.a->s)不会出现错误,而printf(f.a->s)访问的是f.a->s访问的是s对应地址的内容,因为不存在所以造成了段错误。那么,输出结果4又是怎么来的呢?在结构体中,每个成员变量都是相对于结构体其实地址来的,在上面的程序中,f.a的值为0,所以结构体str *a的其实地址为0,而s相对于str *a的偏移量是4,所以输出了结果4。如果把char s[0]改为char *s,那么程序在if(f.a->s)这一行就会挂掉,因为指针的话就会访问指针的内容,因为不存在,所以程序会出现段错误。

结构体对齐的问题

结构体对齐是一个非常重要的概念,涉及到结构体在内存中的存储形式。在结构体中,先看几个概念。

  1. 数据类型自身的对齐值:char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节。
  2. 结构体或类的自身对齐值:其成员中自身对齐值最大的那个值。
  3. 指定对齐值:#pragma pack (value)时的指定对齐值value。
  4. 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值}。

其中,有效对齐值N是最终用来决定数据存放地址方式的值。有效对齐N表示“对齐在N上”,即该数据的“存放起始地址%N=0”。而数据结构中的数据变量都是按定义的先后顺序存放。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐存放,结构体本身也要根据自身的有效对齐值圆整(即结构体成员变量占用总长度为结构体有效对齐值的整数倍)。

struct A{
    int    a;
    char   b;
    short  c;
};
struct B{
    char   b;
    int    a;
    short  c;
};

通过上面的知识可以知道A的大小为8字节,B的大小为12字节。

分类:

更新时间: