是否保证在没有填充位的情况下表示指针结构?

时间:2009-06-25 23:01:30

标签: c arrays struct

我有一个链表,它存储了我的应用程序的设置组:

typedef struct settings {
  struct settings* next;
  char* name;
  char* title;
  char* desc;
  char* bkfolder;
  char* srclist;
  char* arcall;
  char* incfold;
} settings_row;
settings_row* first_profile = { 0 };

#define SETTINGS_PER_ROW 7

当我将值加载到此结构中时,我不希望必须为所有元素命名。我宁愿将它视为一个命名数组 - 值从文件中按顺序加载并逐渐放入结构中。然后,当我需要使用这些值时,我会按名称访问它们。

//putting values incrementally into the struct
void read_settings_file(settings_row* settings){
    char* field = settings + sizeof(void*);
    int i = 0;
    while(read_value_into(field[i]) && i++ < SETTINGS_PER_ROW);
}

//accessing components by name
void settings_info(settings_row* settings){
    printf("Settings 'profile': %s\n", settings.title);
    printf("Description: %s\n", settings.desc);
    printf("Folder to backup to: %s\n", settings.bkfolder);
}

但是我想知道,因为这些都是指针(并且这个结构中只会有指针),编译器是否会为这些值中的任何一个添加填充?它们是否保证按此顺序排列,并且在值之间没有任何内容?我的方法有时会工作,但会间歇性地失败吗?

编辑以澄清

我意识到编译器可以填充结构的任何值 - 但考虑到结构的性质(指针的结构),我认为这可能不是问题。由于32位处理器解决数据的最有效方式是32位块,这就是编译器在结构中填充值的方式(即结构中的int,short,int将在短路后添加2个字节的填充) ,使其成为32位块,并将下一个int与下一个32位块对齐)。但是由于32位处理器使用32位地址(并且64位处理器使用64位地址(我认为)),因为结构的所有值(地址,本质上是有效的)所以填充是完全不必要的。是理想的32位块?

我希望一些内存表示/编译器行为大师可以了解编译器是否有理由填充这些值

8 个答案:

答案 0 :(得分:4)

在许多情况下,指针是自然的字大小,因此编译器不太可能填充每个成员,但这并不是一个好主意。如果你想像对待数组一样对待它,你应该使用数组。

我在这里大声思考,所以可能有很多错误,但也许你可以尝试这种方法:

enum
{
    kName = 0,
    kTitle,
    kDesc,
    kBkFolder,
    kSrcList,
    kArcAll,
    kIncFold,
    kSettingsCount
};

typedef struct settings {
    struct settings* next;
    char *settingsdata[kSettingsCount];
} settings_row;

设置数据:

settings_row myRow;
myRow.settingsData[kName] = "Bob";
myRow.settingsData[kDescription] = "Hurrrrr";
...

阅读数据:

void read_settings_file(settings_row* settings){
    char** field = settings->settingsData;
    int i = 0;
    while(read_value_into(field[i]) && i++ < SETTINGS_PER_ROW);
}

答案 1 :(得分:4)

在POSIX规则下,所有指针(函数指针和数据指针)都需要具有相同的大小;在ISO C下,所有数据指针都可转换为“void *”而不会丢失信息(但函数指针无需转换为“void *”而不会丢失信息,反之亦然)。

因此,如果写得正确,您的代码将起作用。但是,它写得不是很正确!考虑:

void read_settings_file(settings_row* settings)
{
    char* field = settings + sizeof(void*);
    int i = 0;
    while(read_value_into(field[i]) && i++ < SETTINGS_PER_ROW)
        ;
}

假设您使用的是具有8位字符的32位计算机;如果您使用的是64位计算机,那么这个参数并没有那么显着不同。对“field”的赋值都是错误的,因为settings + 4是指向“settings_row”结构数组的第5个元素(从0开始计数)的指针。你需要写的是:

void read_settings_file(settings_row* settings)
{
    char* field = (char *)settings + sizeof(void*);
    int i = 0;
    while(read_value_into(field[i]) && i++ < SETTINGS_PER_ROW)
        ;
}

添加之前的演员至关重要!


C标准(ISO / IEC 9899:1999):

  

6.3.2.3指针

     

指向void的指针可以转换为指向任何不完整或对象的指针   类型。指向任何不完整或对象类型的指针可以转换为指向void的指针   又回来了;结果应该等于原始指针。

     

[...]

     

指向一种类型的函数的指针可以转换为指向另一种函数的指针   打字再回来;结果应该等于原始指针。如果转换了   指针用于调用类型与指向类型不兼容的函数,   行为未定义。

答案 2 :(得分:2)

C标准无法保证。我有一种潜行的怀疑,我现在没有时间检查它,它保证char *字段之间没有填充,即结构中相同类型的连续字段保证是布局兼容的与该类型的数组。但即使如此,你在设置*和第一个char *之间,以及最后一个char *和结构的结尾之间。但是您可以使用offsetof来处理第一个问题,我认为第二个问题不会影响您当前的代码。

但是,你想要的几乎肯定是由你的编译器保证的,它的文档中的某个地方将设置它的结构布局规则,并且几乎肯定会说所有指向数据的指针都是字大小的,并且结构可以是没有额外填充的8个字的大小。但是如果你想编写高度可移植的代码,你必须只使用标准中的保证。

保证字段的顺序。我也不认为你会看到间歇性的失败--AFAIK该结构中每个字段的偏移对于给定的实现(意味着编译器和平台的组合)是一致的。

你可以断言sizeof(settings *)== sizeof(char *)和sizeof(settings_row)== sizeof(char *)* 8。如果两者都成立,则结构中没有任何填充空间,因为不允许字段“重叠”。如果你曾经遇到过他们没有举办的平台,你会发现。

即便如此,如果你想要一个数组,我倾向于使用数组,内联访问器函数或宏来获取各个字段。无论你的诀窍是否有效,你都可以更容易地思考它。

答案 3 :(得分:1)

虽然不重复,但这可能会回答您的问题:

Why isn't sizeof for a struct equal to the sum of sizeof of each member?

应用程序将整个结构体写入文件并再次读取它并不罕见。但是这有可能有一天文件需要在另一个平台上读回,或者另一个版本的编译器以不同方式打包结构。 (虽然这可以通过专门编写的理解原始包装格式的代码来处理。)

答案 4 :(得分:1)

从技术上讲,你只能依赖订单;编译器可以插入填充。如果不同的指针大小不同,或者指针大小不是自然字大小,则可能会插入填充。

实际上,你可以逃脱它。我不推荐它;这是一个坏的,肮脏的把戏。

您可以通过另一级别的间接(不能解决的问题)来实现您的目标,或者使用初始化的临时数组来指向结构的各个成员。

答案 5 :(得分:1)

这不保证,但在大多数情况下都可以正常使用。它不会是间歇性的,它将在具有特定构建的特定平台上工作或不工作。由于您使用的是所有指针,因此大多数编译器都不会使用任何填充。

另外,如果你想要更安全,你可以把它变成一个联盟。

答案 6 :(得分:0)

你不能按照你的方式去做。允许编译器填充结构的任何和所有成员。我不相信它可以重新排序。

大多数编译器都有一个属性,可以应用于结构来打包它(即将其转换为紧密打包存储的集合,没有填充),但缺点是这通常会影响性能。打包标志可能允许您以您想要的方式使用结构,但它可能无法在各种平台上移植。

Padding旨在使目标体系结构上的字段访问尽可能高效。除非必须(即,结构转到磁盘或通过网络),否则最好不要对抗它。)

答案 7 :(得分:0)

在我看来,这种方法产生的问题多于解决的问题。 从现在起六个月后阅读此代码时,您是否仍然了解编译器如何填充结构的所有细微之处? 还有其他人没有写代码吗?

如果必须使用结构,请以规范的方式使用它,然后编写一个函数 分别为每个字段分配值。 您还可以使用数组并创建宏来为索引提供字段名称。

如果您对优化代码过于“聪明”,那么最终会得到更慢的代码,因为编译器也无法对其进行优化。