我有一个链表,它存储了我的应用程序的设置组:
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位块?
我希望一些内存表示/编译器行为大师可以了解编译器是否有理由填充这些值
答案 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)
在我看来,这种方法产生的问题多于解决的问题。 从现在起六个月后阅读此代码时,您是否仍然了解编译器如何填充结构的所有细微之处? 还有其他人没有写代码吗?
如果必须使用结构,请以规范的方式使用它,然后编写一个函数 分别为每个字段分配值。 您还可以使用数组并创建宏来为索引提供字段名称。
如果您对优化代码过于“聪明”,那么最终会得到更慢的代码,因为编译器也无法对其进行优化。