当前位置: 首页 > 知识库问答 >
问题:

指向结构成员的指针是否可以用于访问同一结构的另一个成员?

白浩荡
2023-03-14

我试图理解在将值存储到结构或联合的成员中时,类型双关是如何工作的。

标准N15706.2.6.1(p6)指定

当值存储在结构或联合类型的对象(包括成员对象)中时,与任何填充字节相对应的对象表示的字节采用未指定的值。

所以我把它解释为如果我们有一个对象要存储到一个成员中,这样对象的大小等于sizeof(declared_type_of_the_member)填充,与填充相关的字节将具有未指定的值(即使我们定义了原始对象中的字节)。这是一个例子:

struct first_member_padded_t{
    int a;
    long b;
};

int a = 10;
struct first_member_padded_t s;
char repr[offsetof(struct first_member_padded_t, b)] = //some value
memcpy(repr, &a, sizeof(a));
memcpy(&(s.a), repr, sizeof(repr));
s.b = 100;
printf("%d%ld\n", s.a, s.b); //prints 10100

在我的机器上,尺寸(int) = 4,偏移量(结构first_member_padded_t, b) = 8

打印10100的行为是否为这样的程序定义得很好?我认为是这样的。

共有3个答案

郎飞航
2023-03-14
匿名用户

正如@user694733所说,如果在< code>s.a和< code>s.b之间有填充,< code>memcpy()正在访问一个< code >不能访问的内存区域

int a = 1;
int b;
b = *((char *)&a + sizeof(int));

这是未定义的行为,它基本上是memcpy()内部发生的事情。

子车轶
2023-03-14

在C标准所描述的语言的许多实现中,试图在结构或联合中编写N字节对象会影响该结构或联合中最多N字节的值。另一方面,在支持8位和32位存储但不支持16位存储的平台上,如果有人声明了如下类型:

struct S { uint32_t x; uint16_t y;} *s;

然后执行< code>s-

如果标准包括一种方法,使实现能够指示写入结构或联盟成员是否会干扰超出它们的存储,并且被这种干扰破坏的程序可以拒绝在可能发生这种情况的实现上运行,那将会很有帮助。然而,该标准的作者可能期望对此类细节感兴趣的程序员知道他们的程序应该在哪种硬件上运行,从而知道这种内存干扰是否会成为此类硬件的问题。

不幸的是,现代编译器作者似乎将旨在帮助不寻常硬件实现的自由解释为一种公开的邀请,即使目标是可以在没有这种让步的情况下有效处理代码的平台,也可以获得“创造性”。

宗政昱
2023-03-14

这个问题提得不好。让我们先看看代码:

char repr[offsetof(struct first_member_padded_t, b)] = //some value
memcpy(repr, &a, sizeof(a));
memcpy(&(s.a), repr, sizeof(repr));

首先请注意,repr 是初始化的,因此其中的所有元素都给定了值。

第一个 memcpy 很好 — 它将 字节复制到 repr 中

如果第二个memcpymemcpy(

但是,内存使用

6.7.2.1 15告诉我们,如果指向结构第一个成员的指针“适当转换”,则结果指向该结构。所以,如果我们转换

memcpy((struct first_member_padding t *) &s.a, repr, sizeof repr);

然而,< code>memcpy(

memcpy(&s.a, repr, sizeof repr);
memcpy(&s,   repr, sizeof repr);

这两个语句都将一个空 *传递给 memcpy,并且这两个空 * 彼此具有相同的表示形式并指向相同的字节。现在,我们可以严格地解释标准,以便它们不同,因为后者可用于访问s的所有字节,而前者可能不适用于。然后奇怪的是,我们有两个必然相同的指针,它们的行为不同。

这种对C标准的严格解释在理论上似乎是可能的——指针之间的差异可能出现在优化过程中,而不是在< code>memcpy的实际实现中——但我不知道任何编译器会这样做。请注意,这样的解释与标准的6.2节不一致,该节告诉我们有关类型和表示的内容。解释标准以便< code>(void *)

该问题指出:

我试图理解在将值存储到结构或联合的成员中时,类型双关是如何工作的。

这不是通常使用的类型双关语。从技术上讲,代码确实使用与其定义不同的左值访问s. a(因为它使用memcpy,它被定义为像复制字符类型一样复制,而定义的类型是int),但是字节来自int并且无需修改即可复制,这种复制对象字节的方式通常被视为机械过程;这样做是为了实现复制,而不是重新解释新类型中的字节。“类型双关语”通常是指使用不同的左值来重新解释值,例如编写无符号int和读取浮点数

无论如何,打字双关语并不是这个问题的主题。

标题问:

我们可以在结构或联合成员中存储哪些值?

这个标题似乎与问题的内容不符。标题问题很容易回答:我们可以存储在成员中的值是成员的类型可以表示的值。但问题是继续探讨成员之间的填充。填充不会影响成员中的值。

这个问题引用了标准:

当值存储在结构或联合类型的对象(包括成员对象)中时,与任何填充字节相对应的对象表示的字节采用未指定的值。

并说:

所以我把它解释为好像我们有一个对象要存储到一个成员中,使得对象的大小等于sizeof(declared_type_of_the_member)填充与填充相关的字节将具有未指定的值...

标准中引用的文本意味着,如果< code>s中的填充字节已经设置为某些值,如< code>memcpy,然后我们做< code > s.a = something,则填充字节不再需要保存它们以前的值。

问题中的代码探索了不同的情况。代码memcpy(

6.2.6.1 6意味着,例如,如果我们执行此代码:

char repr[sizeof s] = { 0 };
memcpy(&s, repr, sizeof s); // Set all the bytes of s to known values.
s.a = 0; // Store a value in a member.
memcpy(repr, &s, sizeof s); // Get all the bytes of s to examine them.
for (size_t i = sizeof s.a; i < offsetof(struct first_member_padding_t, b); ++i)
    printf("Byte %zu = %d.\n", i, repr[i]);

那么不一定会打印全零—填充中的字节可能已经改变。

 类似资料:
  • 我正在学习如何在C中使用并写了以下示例: 问:是否保证在所有情况下指向一个结构的指针都是指向它的第一个元素的完全相同的指针? 在这种特殊的情况下,它能像我预期的那样工作,但我不确定它是否能得到保证。编译器可以在开始时插入一些填充吗? 我唯一能找到的关于结构类型布局的是N1570的类型: 结构类型描述了一组按顺序分配的非空成员对象(在某些情况下,还包括一个不完整的数组),每个对象都有一个可选的指定名

  • 在这两个示例中,通过偏移其他成员的指针来访问结构成员是否会导致未定义/未指定/实现定义的行为? C11§6.7.2.1第14段似乎表明,这应该是实施定义: 结构或联合对象的每个非位字段成员都以适合其类型的实现定义方式对齐。 后来又说: 结构对象中可能有未命名的填充,但在其开头没有。 但是,如下所示的代码似乎相当常见: 该标准似乎保证 与 和< code >( 原始应用程序正在考虑从一个结构字段到另

  • 我遇到了一个语法问题。看另一个StackOverflow答案并不能给出一个适用于我的问题的答案。至少不是我能理解的。 我的计划程序类: 这一切都编译得很好,但问题在于调用该函数中的成员函数指针: 编译这个会给我带来错误; 其他尝试: 有人能帮我解释一下我缺了什么知识吗?

  • C标准规定: 指向结构对象的指针,经过适当的强制转换,指向其初始成员(如果该成员是位字段,则指向其所在的单元),反之亦然。 如果有问题的结构的第一个成员是匿名结构/联合,是否有任何可能(并且定义良好)的方法在C11中执行这种“合适的转换”?或者如果包含的结构是匿名的,则执行“反之亦然”的反向转换? 我猜想,转换为与匿名结构具有相同成员序列的非匿名结构会使它无法很好地定义,因为它们不兼容,因此不能保

  • 问题内容: 我不理解以下代码的行为。在创建作为结构指针切片的匹配结构列表时,代码始终会打印原始数组的最后一个元素(实际上不是匹配项),它会打印12和12。但是,如果将匹配项更改为[]窗口小部件代替[] * Widget,然后将输出10和11。 为什么是这样? 问题答案: 那是因为当您使用指针时,您将添加到数组。 请注意,实际上这是循环中使用的局部变量,因此,这不是您要添加到数组中的地址。 (即使变

  • 我正在学习链表,以及如何在C中使用结构和指针创建链表。下面我举一个例子。据我所知,被调用的将头节点所在的结构的开始内存位置作为参数传递。push()函数的参数将结构节点作为指向指针的指针,因此它作为引用传递,而不是实际副本。因此,我们的的第一个指针只是指向头部节点的内存位置的指针,第二个指针指向该值,该值是头部节点指向的下一个内存位置。我们通过为结构节点分配一些内存,在结构节点内创建一个名为new