char [] + memcpy()是否违反严格别名?

时间:2017-01-07 04:37:56

标签: c undefined-behavior 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 == 3float_result == 10.0f)。将memcpy更改为正常的指针取消引用(int ret = *(int *)d->buf)也可以正常工作,没有任何警告。

我在严格别名上阅读的所有来源都说您可以将任何类型读为char *(所以我认为这意味着set应该没问题),但是您无法阅读与任何其他类型char *一样(不确定get是否正常)。我误解了这个规则吗?

1 个答案:

答案 0 :(得分:1)

在C89下,memcpy的行为类似于使用unsigned char*读取源的每个字节,并使用unsigned char*写入目标的每个字节;由于字符指针可用于访问其他任何内容,因此memcpy通用以进行数据转换。

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

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

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

相关问题