【问题标题】:Assumption of structure padding in CC中结构填充的假设
【发布时间】:2021-07-23 04:01:31
【问题描述】:

我在学习 C 中的结构填充时遇到了this video

基本上它说如果我有一个结构

struct abc {
    char a;    // 1 byte
    char b;    // 1 byte
    int c;     // 4 bytes
} var;

那么,不要像这样存储结构体(c,...,c 表示 c 的四个字节;|| 是单词边界;_ 是字节的位置)

_  _  _  _ || _  _  _  _
a  b  c  c    c  c    

在b之后会填充两个字节的空白空间,结果是(e表示空)

_  _  _  _ || _  _  _  _
a  b  e  e    c  c  c  c

这样CPU可以在一个CPU周期内得到int c。

但是,这确实建立在 struct 的第一个成员(在我的例子中为 a)将在单词边界之后立即存储的假设之上。总是这样吗?

【问题讨论】:

  • 你使用的是哪个编译器?
  • 你的目标是哪个 CPU?
  • 听起来你感兴趣的是结构的对齐要求。
  • 我觉得重要的是要注意在这里起作用的不是单词边界,而是对齐要求。即使在一个单词中也可以有填充。例如,尝试一个以char 作为第一个元素,short 作为第二个元素的结构。
  • @Tony 我正在使用来自 Segger Embedded Studio 的默认 GCC。

标签: c memory struct padding


【解决方案1】:

但是,这确实建立在 struct 的第一个成员将在字边界之后立即存储的假设之上。总是这样吗?

是的。

定义结构类型时,结构的对齐要求至少是其成员最严格的对齐要求。例如,如果一个结构具有对齐要求为 1 个字节、8 个字节和 4 个字节的成员,则该结构的对齐要求将是 8 个字节。定义结构时,编译器会自动计算出来。 (从技术上讲,C 标准可能允许编译器对结构进行更大的对齐——我没有看到任何反对它的规则——但实际上并没有这样做。)

然后,每当 C 实现为结构对象保留内存时(如定义该类型的对象,例如 struct foo x),它将确保内存按照该结构的要求对齐。这导致成员的对齐要求也得到满足。当程序使用malloc 分配内存时,返回的内存总是根据请求大小的任何对象对齐。

(如果你在程序中做了任何“有趣的事情”来为对象设置你自己的内存位置,例如将一个放在分配给malloc的内存中间,你有责任获得正确的对齐方式。)

此外,如有必要,结构将在末尾填充,以便其总大小是该对齐要求的倍数。然后,在这些结构的数组中,数组的每个连续元素也将从正确对齐的位置开始。

【讨论】:

  • 谢谢!您能否详细说明“结构的对齐要求将至少是其成员最严格的对齐要求”?一个例子将不胜感激。
  • @jleng: 如果结构的成员有1字节、1字节、4字节、8字节、1字节和4字节的对齐要求,那么结构的对齐要求将为8字节,因为这是成员最严格的对齐要求。
  • @jleng:大多数平台要求多字节对象“对齐”,以便它们从 2 或 4 或 8 倍数的地址开始(取决于平台、类型和其他考虑)。如果struct 的成员必须对齐,使其地址为4 的倍数,则struct 对象本身也将对齐,使其地址为4 的倍数。
  • @JohnBode:谢谢。这消除了我留下的所有困惑。
【解决方案2】:

结构类型的对象的地址总是等于对象的第一个成员的地址。

来自 C 标准(6.7.2.1 结构和联合说明符)

15 在结构对象中,非位域成员和单元 位域所在的地址按顺序增加 在其中声明它们。 指向结构对象的指针, 适当转换,指向其初始成员(或者如果该成员是 一个位域,然后到它所在的单元),反之亦然。 结构对象中可能有未命名的填充,但在其 开始。

这是一个演示程序

#include <stdio.h>

int main(void) 
{
    struct abc
    {
        char a;
        char b;
        int c;
    } abc = { 'A', 'B', 3 };
    
    printf( "&abc = %p, &abc.a = %p\n", ( void * )&abc, ( void * )&abc.a );
    
    struct abc *p = &abc;
    
    printf( "*( char * )p = %c\n", *( char * )p );
    
    return 0;
}

程序输出可能看起来像

&abc = 0x7ffe8cfad6c0, &abc.a = 0x7ffe8cfad6c0
*( char * )p = A

【讨论】:

    【解决方案3】:

    这是编译器进行的优化,因为它对 CPU 来说更容易。大多数编译器应该允许您禁用它。例如,在 GCC 中,您可以使用 __attribute__((packed))

    另见How to override C compiler aligning word-sized variable in struct to word boundary

    【讨论】:

      猜你喜欢
      • 2017-03-05
      • 2023-04-03
      • 2015-04-25
      • 1970-01-01
      • 2014-10-12
      • 2011-10-21
      • 1970-01-01
      • 1970-01-01
      • 2011-09-08
      相关资源
      最近更新 更多