字符[]和#x2B;memcpy()是否违反严格别名?

字符[]和#x2B;memcpy()是否违反严格别名?,c,undefined-behavior,strict-aliasing,C,Undefined Behavior,Strict Aliasing,我在结构中使用char数组来保存一些通用数据,如下所示(输入类型可能是大小未知的结构,因此我不能仅使用联合;此代码非常简化): 如果我从未尝试编写一个float,然后将数据作为int读取,或者反之亦然,那么这个代码在C(99)中定义得好吗 使用GCC编译不会产生警告,运行代码会给出预期的行为(int\u result==3,float\u result==10.0f)。将memcpy更改为正常指针解引用(int-ret=*(int*)d->buf)也可以正常工作,无任何警告 我读过的所有关于严格

我在结构中使用
char
数组来保存一些通用数据,如下所示(输入类型可能是大小未知的结构,因此我不能仅使用联合;此代码非常简化):

如果我从未尝试编写一个
float
,然后将数据作为
int
读取,或者反之亦然,那么这个代码在C(99)中定义得好吗

使用GCC编译不会产生警告,运行代码会给出预期的行为(
int\u result==3
float\u result==10.0f
)。将
memcpy
更改为正常指针解引用(
int-ret=*(int*)d->buf
)也可以正常工作,无任何警告


我读过的所有关于严格别名的资料都说,你可以将任何类型读作
char*
(因此我认为这意味着
set
应该可以),但你不能将
char*
读作任何其他类型(不太确定
get
是否可以)。我是否误解了规则?

在C89下,memcpy的行为类似于使用
无符号字符*
读取源的每个字节,并使用
无符号字符*
写入目标的每个字节;由于字符指针可用于访问任何其他内容,这使得
memcpy
在数据转换方面具有通用性

C99为
memcpy
添加了一些新的限制,允许在目标对象具有声明的类型,或用于读取目标对象的所有非字符指针的有效类型与源的有效类型一致的情况下使用它,但是,将没有声明类型的对象保留在仅可使用源类型读取的状态中。我认为C11并没有以任何有意义的方式放松这些限制

您的代码不应受到memcpy规则的影响,因为每个memcpy操作要么写入具有声明类型的对象,要么写入仅通过memcpy写入具有声明类型的对象的存储。C99的memcpy规则的主要问题出现在代码需要在不知道下一次读取对象的类型的情况下就地更新对象时


例如,在
int
long
具有相同32位表示的系统上,应该可以编写一个函数,将数据加载到
int[]
long[]
中,而不必知道它正在接收哪种指针(机器操作的顺序在任何一种情况下都是相同的)。如果代码将一些数据读入一个临时的
int[]
,然后使用
memcpy
将其移动到最终目的地,如果目的地是类型为
int[]
long[]的实际声明对象,则该顺序将由标准保证工作
,或者如果它是一个分配存储区域,将被读取为
int[]
,但如果它是一个分配存储区域,下一个将被读取为
long

,则在到达
*(int*)之前,这是可以保证工作的d->buf
。此时,所有赌注都被取消,因为
int
可能有一个对齐约束,
d->buf
可能满足,也可能不满足。@AndrewSun否。对齐约束意味着您将在某些处理器和编译器而不是其他处理器和编译器中得到一个错误。严格的别名禁止通过两个不同的非字符类型。你没有这样做。@AndrewSun另一方面,这不是一个明智的做法。这正是
union
s的目的,它们将生成更高效的代码,因为它们
将保证所有字段类型的对齐,所以不需要字节复制。@chux字节布局在这方面是否重要这种情况?我是
memcpy
ing from
int->char[]->int
(或
float->char[]->float
)而从未接触过缓冲区中的字节。@AndrewSun我读错了你的问题“如果我从不尝试写一个float,然后以int或反之亦然的形式读取数据,这段代码在C(99)中定义得好吗?”我错过了“从不”`关于我之前的评论,如果我理解正确,只要源类型和目标类型相同,将一些数据
memcpy
发送到中间缓冲区,然后
memcpy
将其发送到目标(
T->char[]->T
)?如果源具有有效类型,目标没有声明的类型,并且下次使用源以外的类型读取目标,则会出现问题。在您的示例中,从
char[]复制数据的
memcpy
操作
将其写入具有声明类型的局部变量。写入的标准允许实现假定memcpy来自
char[]
对从
memcpy
接收的对象的访问不会影响使用
int*
float*
等访问的任何对象。虽然实现会这样做似乎很荒谬……但实现使用别名规则来证明许多同样荒谬的行为是正确的。
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;
}