c - 是否可以使用指向某个结构成员的指针来访问同一结构的另一个成员?

标签 c language-lawyer type-punning

我试图了解将值存储到结构或联合的成员中时类型处理的工作方式。

标准N1570 6.2.6.1(p6)指定


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


因此,我将其解释为好像我们有一个要存储到成员中的对象,使得该对象的大小等于sizeof(declared_type_of_the_member) + padding,与填充相关的字节将具有未指定的值(即使我们在原始对象定义)。这是一个例子:

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


在我的计算机上sizeof(int) = 4, offsetof(struct first_member_padded_t, b) = 8

是否为此类程序定义了10100打印行为?我是这样。

最佳答案

memcpy呼叫做什么
这个问题提出得不好。首先看一下代码:

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

首先请注意,repr已初始化,因此其中的所有元素都被赋予了值。
第一个memcpy很好-将a的字节复制到repr
如果第二个memcpymemcpy(&s, repr, sizeof repr);,它将把字节从repr复制到s。这会将字节写入s.a,并且由于repr的大小,会将字节写入s.as.b之间的任何填充。根据C 2018 6.5 7和该标准的其他规定,允许访问对象的字节(“访问”表示根据3.1 1进行读取和写入)。因此,将此副本复制到s很好,并且导致s.a采用与a相同的值。
但是,memcpy使用&(s.a)而不是&s。它使用s.a的地址而不是s的地址。我们知道,将s.a转换为字符类型的指针将使我们能够访问s.a的字节(6.5 7和更多)(并将其传递给memcpy的效果与这种转换相同,如< cc>被指定为具有复制字节的效果),但尚不清楚它是否允许我们访问memcpy中的其他字节。换句话说,我们有一个问题,我们是否可以使用s访问除&s.a中的字节以外的其他字节。
6.7.2.1 15告诉我们,如果“适当地转换”指向结构的第一个成员的指针,则结果指向该结构。因此,如果将s.a转换为指向&s.a的指针,它将指向struct first_member_padding_t,并且我们当然可以使用指向s的指针来访问s中的所有字节。因此,这也将得到很好的定义:
memcpy((struct first_member_padding t *) &s.a, repr, sizeof repr);

但是,s仅将memcpy(&s.a, repr, sizeof repr);转换为&s.a(因为声明void *接受memcpy,所以void *在函数调用期间自动转换),而不是指向结构类型的指针。那是合适的转换吗?请注意,如果我们执行&s.a,它将把memcpy(&s, repr, sizeof repr);转换为&s。 6.2.5 28告诉我们,指向void *的指针与指向字符类型的指针具有相同的表示形式。因此,请考虑以下两个语句:
memcpy(&s.a, repr, sizeof repr);
memcpy(&s,   repr, sizeof repr);

这两个语句都将void传递给void *,并且这两个memcpy彼此具有相同的表示形式,并指向同一字节。现在,我们可能会严格而严格地解释标准,以使它们有所不同,因为后者可以用于访问void *的所有字节,而前者则不能。然后奇怪的是,我们有两个必然相同的指针,它们的行为不同。
从理论上讲,对C标准进行如此严格的解释似乎是可能的-指针之间的差异可能在优化过程中出现,而不是在s的实际实现中出现-但我不知道有任何编译器会这样做。请注意,这种解释与标准的第6.2节不一致,后者告诉我们有关类型和表示形式的信息。解释标准,使memcpy(void *) &s.a的行为不同,意味着具有相同值和类型的两个事物的行为可能不同,这意味着值除了其值和类型之外还包含其他内容,这似乎不是目的。一般为6.2或标准。
打字
问题指出:

我试图了解将值存储到结构或联合的成员中时类型处理的工作方式。

这不是类型修饰,因为该术语是常用的。从技术上讲,代码确实使用与定义不同类型的左值访问(void *) &s(因为它使用了s.a,它被定义为像字符类型一样被复制,而定义的类型为memcpy),但是字节源于int且未经修改即被复制,这种复制对象字节的过程通常被视为机械过程;这样做是为了实现副本,而不是重新解释新类型的字节。 “类型绑定”通常是指使用不同的左值来重新解释该值,例如编写int和读取unsigned int
无论如何,类型绑定并不是问题的实质。
会员价值观
标题问:

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

这个标题似乎与问题的内容背道而驰。标题问题很容易回答:我们可以存储在成员中的值是成员类型可以代表的值。但是问题继续探讨成员之间的填充。填充不影响成员中的值。
填充采用未指定的值
问题引用标准:

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

并说:

因此,我将其解释为好像我们有一个要存储到成员中的对象,以便该对象的大小等于s float与填充相关的字节将具有未指定的值…

标准中带引号的文本表示,如果izeof(declared_type_of_the_member) + padding中的填充字节已设置为某些值(如s一样),然后再执行memcpy,则不再需要填充字节来保留其先前的字节。价值观。
问题中的代码探讨了另一种情况。代码s.a = something;不在6.2.6.1 6.含义中将值存储在结构的成员中。它不存储在成员memcpy(&(s.a), repr, sizeof(repr));s.a中。它正在复制字节,这与6.2.6.1中讨论的内容有所不同。
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 - 是否可以使用指向某个结构成员的指针来访问同一结构的另一个成员?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55103332/

相关文章:

c++ - 通用 lambda 的熟悉模板语法

c++ - 在这种情况下是 clang 错误、gcc 错误还是两者都错误 - 用成员指针抛弃 constness

c++ - C++函数匹配(内置转换和类类型转换的优先级)

linked-list - 是否可以在 Rust 中将一个结构的内存与另一个结构相关联?

c++ - std::memcpy 或显式 char 值赋值 - 在 C++ 中相等且合法

c - 在 C 中使用#include <stdbool.h>

c - 写入文件并使用带循环的 if 语句

c - 什么是双关语?类型双关如何与 C 中的 union 一起使用?

c++ - 开发类似python的小型语言时的缩进控制

c - 如何将数组包含到 C 函数定义中