首页 新闻 会员 周边 捐助

C语言结构体柔性数组的偏移量问题

0
悬赏园豆:200 [已解决问题] 解决于 2024-09-19 11:55

C11标准6.7.2.1 P18有这样一段话:
However, when a . (or ->) operator has a left operand that is
(a pointer to) a structure with a flexible array member and the right operand names that
member, it behaves as if that member were replaced with the longest array (with the same
element type) that would not make the structure larger than the object being accessed;
the offset of the array shall remain that of the flexible array member, even if this would differ
from that of the replacement array.

翻译过来大概是:
然而,当一个.(或 ->)运算符的左操作数是(一个指向)具有 柔性数组成员 的结构体(的指针),并且右操作数命名了该成员时,它的行为表现得就好像柔性数组成员被 替换为 最长的数组,只要这个数组不会使结构所占的存储大于 被访问的对象。
这个替换数组的偏移量还是原来柔性数组成员的偏移量,即使替换数组的偏移量可能会不同。

我可以理解这段话的绝大部分。但是,对于在最后一句话:

这个替换数组的偏移量还是原来柔性数组成员的偏移量,即使替换数组的偏移量可能会不同。

我无法理解。

举例而言,

示例: 在以下声明之后
struct s { int n; double d[ ]; };
结构struct s具有一个柔性数组成员d。一个典型的使用方式是:
int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
假设对malloc的调用成功,p指向的对象的行为就好像p被声明为:
struct { int n; double d[m]; } *p;

柔性数组被视为仿佛像替换数组double d[m]一样。

但是因为柔性数组和替换数组的元素类型一样,它们的对齐要求一致,我使用offsetof
求得的柔性数组和替换数组的偏移量总是相同。
那么替换数组的偏移量怎么可能不同于柔性数组的偏移量?

先谢谢大佬的解答。如果可以举出一个例子,那就更好了

HXZ778的主页 HXZ778 | 初学一级 | 园豆:4
提问于:2024-06-28 17:35
< >
分享
最佳答案
0
typedef struct _s1 {
    char a;      // 7 bytes internal padding after “char a” to make the address of “b” 8 bytes aligned.
    double b; // assume "double" is 8 bytes
    char c;     // no padding after this element
    char d[1]; // 6 bytes trailing padding after “char d[1]” to make the whole structure 8 bytes aligned.
} s1_t;

//offsetof(s1_t, d) is 17, sizeof(s1_t) is 24.

//Now consider the following structure:
typedef struct _s2 {
    char a;      // 7 bytes internal padding after “char a” to make the address of “b” 8 bytes aligned.
    double b; // assume "double" is 8 bytes
    char c;     // 7 bytes trailing padding after this element
    char d[];  // no trailing padding. "d" should start at 24th bytes.
} s2_t;

可能有的编译器是按上面说的这样实现的,会有不同,不过我测试gcc,这俩是没有区别的.

收获园豆:200
www378660084 | 小虾三级 |园豆:1143 | 2024-06-28 19:12

我也已经用gcc测试过相当多的代码。
我也希望是因为实现的原因,但是
“ there are circumstances in which this equivalence is broken; in particular, the offsets of member d might not be the same".
(在某些情况下,这种等价性会被打破;特别是,成员d的偏移量可能不相同)

并不是我钻牛角尖,他完全可以这样用更专业的术语说:
there are implementation in which this equivalence is broken; in particular, the offsets of member d might not be the same.
( implementation:实现)
不是吗?

HXZ778 | 园豆:4 (初学一级) | 2024-06-28 19:23

你可以这样想,“在某些情况下,这种等价性会被打破”。打破这种等价性是很容易的。
(这里不详细列举代码,我相信你应该知道)
打破这种等价性的代码就是“这种情况”的体现。
同样的,打破这种等价性的原因是因为“成员d的偏移量可能不相同”也应当对应于一种代码,只不过它还未被我们发掘。至少,我找不出这样的代码。
我相信它一定存在,只不过那是很少见到情况,但我仍想知道。

HXZ778 | 园豆:4 (初学一级) | 2024-06-28 19:29
    struct A
    {
        int a;
        char c;
        char b[];
    };
    struct A* a = malloc(sizeof(struct A) + sizeof(char [10]));

能想到的也就这种malloc的情况,不过这个也不准确. 替换数组的偏移是 sizeof(struct A)=8, offsetof(struct A, b)=5.
你说的这句描述应该是C99遗留下来的.开始C99的时候,柔性数组按标准必须要在结构体后面的,但是gcc这些实现为了方便没按标准来,到C11的时候,标准也屈服了,不再要求柔性数组必须在结构体的后面了. 所以之前的那个例子就不成立了..

www378660084 | 园豆:1143 (小虾三级) | 2024-06-28 20:24

@www378660084:
我引用的原文全部来自C11
C11里也要求了柔性数组必须是结构体的最后一个元素。
正如标准所说:

As a special case, the last element of a structure with more than one named member may
have an incomplete array type;

您举的例子成功打破了这种等价性(替换数组可以不止有10个元素),这很好,但它仍不是完美的结果。

因为我不明白替换数组的偏移为什么是8,
事实上,
struct A
{
int a;
char c;
char b[10];
};
替换数组的偏移量仍然是5。
即offsetof(struct A, b),我不明白为什么是sizeof(struct A)=8。

HXZ778 | 园豆:4 (初学一级) | 2024-06-28 21:12

@HXZ778: C99要求FAR不只是最后一个元素,还要求FAR的偏移必须等于sizeof(struct), 所以C99上,FAR的偏移在结构体有padding的时候,很有可能跟非柔性数组偏移不同.后面的语句是C11从C99继承过来的,确实在C11上找不出什么例子,也没看到哪个编译器完全按照C99去实现,所以测试代码也找不到.不过按照C99标准,下面的例子是成立的

typedef struct _s1 {
    char a;      // 7 bytes internal padding after “char a” to make the address of “b” 8 bytes aligned.
    double b; // assume "double" is 8 bytes
    char c;     // no padding after this element
    char d[1]; // 6 bytes trailing padding after “char d[1]” to make the whole structure 8 bytes aligned.
} s1_t;

//offsetof(s1_t, d) is 17, sizeof(s1_t) is 24.

//Now consider the following structure:
typedef struct _s2 {
    char a;      // 7 bytes internal padding after “char a” to make the address of “b” 8 bytes aligned.
    double b; // assume "double" is 8 bytes
    char c;     // 7 bytes trailing padding after this element
    char d[];  // no trailing padding. "d" should start at 24th bytes.
} s2_t;

C99:As a special case, the last element of a structure with more than one named member may
have an incomplete array type; this is called a flexible array member. With two
exceptions, the flexible array member is ignored. First, <<<the size of the structure shall be
equal to the offset of the last element>>> of an otherwise identical structure that replaces the
flexible array member with an array of unspecified length.106) Second, when a . (or ->)
operator has a left operand that is (a pointer to) a structure with a flexible array member
and the right operand names that member, it behaves as if that member were replaced
with the longest array (with the same element type) that would not make the structure
larger than the object being accessed; the offset of the array shall remain that of the
flexible array member, even if this would differ from that of the replacement array. If this
array would have no elements, it behaves as if it had one element but the behavior is
undefined if any attempt is made to access that element or to generate a pointer one past
it.
C11:As a special case, the last element of a structure with more than one named member may
have an incomplete array type; this is called a flexible array member. In most situations,
the flexible array member is ignored. In particular, the size of the structure is as if the
flexible array member were omitted except that it may have more trailing padding than
the omission would imply. Howev er, when a . (or ->) operator has a left operand that is
(a pointer to) a structure with a flexible array member and the right operand names that
member, it behaves as if that member were replaced with the longest array (with the same
element type) that would not make the structure larger than the object being accessed; the
offset of the array shall remain that of the flexible array member, even if this would differ
from that of the replacement array. If this array would have no elements, it behaves as if
it had one element but the behavior is undefined if any attempt is made to access that
element or to generate a pointer one past it.

www378660084 | 园豆:1143 (小虾三级) | 2024-06-29 15:24

@www378660084:
您引用的C99中的文本很有参考价值。
设想一下,如果是在C99在中,这样的例子显而易见。
现在,我有点开始怀疑C11中存不存在这样的例子,也许真的只是历史遗留的实现问题。

HXZ778 | 园豆:4 (初学一级) | 2024-06-29 21:13
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册