0

我的代码有一个结构类型定义如下:

typedef struct
{
   Structure_2 a[4];
    UCHAR b;
    UCHAR c;
}Structure_1;

其中 Structure_2 的定义如下:

typedef struct
{
    ULONG  x;
    USHORT y;
    UCHAR  z;
}Structure_2;

代码中还有两个函数。第一个(名为 setter)声明了一个“Structure_1”类型的结构并用数据填充它:

void setter (void)
{
    Structure_1        data_to_send ;
    data_to_send.a[0].x = 0x12345678;
    data_to_send.a[0].y = 0x1234;
    data_to_send.a[0].z = 0x12; 
    data_to_send.a[1].x = 0x12345678;
    data_to_send.a[1].y = 0x1234;
    data_to_send.a[1].z = 0x12; 
    data_to_send.a[2].x = 0x12345678;
    data_to_send.a[2].y = 0x1234;
    data_to_send.a[2].z = 0x12; 
    data_to_send.a[3].x = 0x12345678;
    data_to_send.a[3].y = 0xAABB;
    data_to_send.a[3].z = 0x12; 
    data_to_send.b =0;
    data_to_send.c = 0;
    getter(&data_to_send);
}

编译器将 data_to_send 保存在内存中,如下所示:

在此处输入图像描述

第二个命名为 getter:

void getter (Structure_1 * ptr_to_data)
{
    UCHAR R_1 = ptr_to_data -> b;
    UCHAR R_2 = ptr_to_data -> c;
    /* The remaining bytes are received */
}

我希望 R_1 的值为“00”,而 R_2 的值为“00”。

但是发生的是编译器会像这样翻译以下两行:

 /* Get the data at the address  ptr_to_data -> b,
    which equals the start address of structure + 28 which contains the        
    value “AA”, and hence R_1 will have “AA” */

UCHAR R_1 = ptr_to_data -> b;  

/* Get the data at the address  ptr_to_data -> c, 
   which equals the start *address of structure + 29 which contains the   
   value “BB”, and hence R_2 will *have “BB” */
UCHAR R_2 = ptr_to_data -> c;  

编译器在将结构保存在堆栈中的同时添加了填充 b/yte,但是当它开始读取它时,它忘记了它做了什么(并在读取中包含了填充字节)。

我如何通知编译器在读取结构元素时应该跳过填充字节?

我不想解决这个问题,我很想知道为什么编译器会这样?

我的编译器是 GreenHills,我的目标是 32 位

4

2 回答 2

2

我如何通知编译器在读取结构元素时应该跳过填充字节?

简短的回答:你不能
编译器不会忽略结构中包含的内容。但是,您可以控制它将如何处理结构中的内容。

我很想知道为什么编译器会这样?

简短的回答: 数据对齐
需要考虑的两个问题:数据对齐边界和数据结构填充。您对每个都有一些控制: 数据对齐是您的编译器看到它所看到的内容的原因。数据对齐意味着将数据放置在等于字大小的某个倍数的内存地址(32 位环境为 4 个字节) 即使您不使用显式填充,数据也会被存储以便观察这些边界,并且大小结构的 将指示使用的总字节空间中的填充。

结构填充- 放置在结构中的无意义字节有助于将大小对齐为字长的倍数。您的示例代码中有此内容。

您可以使用编译指示宏使编译器以某种方式预处理(编译前解析)结构的打包:示例 #pragma pack(n) 只是设置新的对齐方式。或者,#pragma pack() 将对齐设置为编译开始时生效的对齐。

示例

#pragma pack(push)  /* push current alignment to stack */
#pragma pack(1)     /* set alignment to 1 byte boundary */

struct MyPackedData
{
    char Data1;
    long Data2;
    char Data3;
};

#pragma pack(pop)   /* restore original alignment from stack */   

注意:pack(n) 的 n 单位是byte。n 的值是特定于编译器的,例如 MSVC 通常是 1、2、4、8 和 16。

问题: 如果您使用 prama pack 宏,它们是否在 getter()/setter() 函数之间使用一致的包值?(归功于@alain)

但是同样,这不会导致编译器忽略结构的内容,只会以不同的方式处理它。

有关您观察的根本原因的更多信息,请参阅此处此处的信息。

于 2015-08-05T18:56:14.687 回答
2

我对@ryykers 好答案的评论的较长版本:

您在问题中显示的代码是完全有效的,绝对没有理由在读取 struct 成员时得到错误的值getter,前提是

  • 没有铸造
  • 相同的包装规则生效

否则你正在使用的编译器将被严重破坏。

设置打包规则的方式因编译器而异,它们没有标准化,所以可能没有命名#pragma pack

“通常”,没有理由干扰结构打包,但一个原因是通过网络或文件发送数据。当结构完全没有填充时,您可以将它们强制转换为void *orchar *并将结构直接传递给“发送”函数,例如:

send((void *)&data_to_send, sizeof(data_to_send));

您问题中的变量名称data_to_send暗示这可能是此代码中发生的情况。我并不是说这是一种好的做法,但它很常见,因为您不必编写序列化代码。

于 2015-08-05T20:05:17.170 回答