char[] + memcpy() 是否违反严格别名?
Does char[] + memcpy() violate strict aliasing?
我在结构中使用 char
数组来保存一些通用数据,就像这样(输入类型可能是未知大小的结构,所以我不能只使用联合;这段代码已大大简化):
typedef struct {
char buf[256];
} data;
void data_set_int(data *d, int a) {
memcpy(d->buf, &a, sizeof(a));
}
int data_get_int(data *d) {
int ret;
memcpy(&ret, d->buf, sizeof(ret));
return ret;
}
void data_set_float(data *d, float a) {
memcpy(d->buf, &a, sizeof(a));
}
float data_get_float(data *d) {
float ret;
memcpy(&ret, d->buf, sizeof(ret));
return ret;
}
int main(void) {
data d;
data_set_int(&d, 3);
int int_result = data_get_int(&d);
data_set_float(&d, 10.0f);
float float_result = data_get_float(&d);
return 0;
}
如果我从不尝试编写 float
然后将数据读取为 int
或反之亦然,此代码在 C(99) 中是否定义明确?
使用 GCC 编译不会产生任何警告,并且 运行 代码给出了预期的行为(int_result == 3
、float_result == 10.0f
)。将 memcpy
更改为普通指针取消引用 (int ret = *(int *)d->buf
) 也可以正常工作,没有任何警告。
我读过的关于严格别名的所有资料都说你可以将任何类型读作 char *
(所以我认为这意味着 set
应该没问题),但你不能读a char *
与任何其他类型一样(不确定 get
是否合适)。我是否误解了规则?
在 C89 下,memcpy 的行为类似于使用 unsigned char*
读取源的每个字节,并使用 unsigned char*
写入目标的每个字节;由于字符指针可用于访问任何其他内容,因此 memcpy
可用于数据转换。
C99 对 memcpy
添加了一些新限制,仍然允许在目标对象具有已声明类型或所有非字符指针的有效类型将要访问的情况下使用用于读取目标对象与源的有效类型一致,但将没有声明类型的对象留在只能使用源类型读取的状态。我不认为 C11 以任何有意义的方式放松了这些限制。
您的代码不应受到 memcpy 规则的影响,因为每个 memcpy 操作要么写入具有声明类型的对象,要么写入存储,而存储通过 memcpy 写入具有声明类型的对象时只会显示为红色。 C99 的 memcpy 规则的主要问题情况发生在代码需要就地更新对象而不知道下一次读取对象的类型时。
例如,在 int
和 long
具有相同的 32 位表示的系统上,应该可以编写一个函数来将数据加载到 int[]
或 long[]
而不必知道它接收的是哪种指针(两种情况下机器操作的顺序都是相同的)。如果代码将一些数据读入临时 int[]
,然后使用 memcpy
将其移动到最终目的地,如果目的地是实际声明的类型 int[]
或 long[]
,或者如果它是将被读取为 int[]
的已分配存储区域,但如果它是接下来将被读取的已分配存储区域,则不能保证工作读作 long
.
我在结构中使用 char
数组来保存一些通用数据,就像这样(输入类型可能是未知大小的结构,所以我不能只使用联合;这段代码已大大简化):
typedef struct {
char buf[256];
} data;
void data_set_int(data *d, int a) {
memcpy(d->buf, &a, sizeof(a));
}
int data_get_int(data *d) {
int ret;
memcpy(&ret, d->buf, sizeof(ret));
return ret;
}
void data_set_float(data *d, float a) {
memcpy(d->buf, &a, sizeof(a));
}
float data_get_float(data *d) {
float ret;
memcpy(&ret, d->buf, sizeof(ret));
return ret;
}
int main(void) {
data d;
data_set_int(&d, 3);
int int_result = data_get_int(&d);
data_set_float(&d, 10.0f);
float float_result = data_get_float(&d);
return 0;
}
如果我从不尝试编写 float
然后将数据读取为 int
或反之亦然,此代码在 C(99) 中是否定义明确?
使用 GCC 编译不会产生任何警告,并且 运行 代码给出了预期的行为(int_result == 3
、float_result == 10.0f
)。将 memcpy
更改为普通指针取消引用 (int ret = *(int *)d->buf
) 也可以正常工作,没有任何警告。
我读过的关于严格别名的所有资料都说你可以将任何类型读作 char *
(所以我认为这意味着 set
应该没问题),但你不能读a char *
与任何其他类型一样(不确定 get
是否合适)。我是否误解了规则?
在 C89 下,memcpy 的行为类似于使用 unsigned char*
读取源的每个字节,并使用 unsigned char*
写入目标的每个字节;由于字符指针可用于访问任何其他内容,因此 memcpy
可用于数据转换。
C99 对 memcpy
添加了一些新限制,仍然允许在目标对象具有已声明类型或所有非字符指针的有效类型将要访问的情况下使用用于读取目标对象与源的有效类型一致,但将没有声明类型的对象留在只能使用源类型读取的状态。我不认为 C11 以任何有意义的方式放松了这些限制。
您的代码不应受到 memcpy 规则的影响,因为每个 memcpy 操作要么写入具有声明类型的对象,要么写入存储,而存储通过 memcpy 写入具有声明类型的对象时只会显示为红色。 C99 的 memcpy 规则的主要问题情况发生在代码需要就地更新对象而不知道下一次读取对象的类型时。
例如,在 int
和 long
具有相同的 32 位表示的系统上,应该可以编写一个函数来将数据加载到 int[]
或 long[]
而不必知道它接收的是哪种指针(两种情况下机器操作的顺序都是相同的)。如果代码将一些数据读入临时 int[]
,然后使用 memcpy
将其移动到最终目的地,如果目的地是实际声明的类型 int[]
或 long[]
,或者如果它是将被读取为 int[]
的已分配存储区域,但如果它是接下来将被读取的已分配存储区域,则不能保证工作读作 long
.