2016-03-11 40 views
3

相关的,但是从有些不同,Do any compilers transfer effective type through memcpy/memmove类型无关的memcpy在C99

在C89,memcpymemmove需要表现得好像源和目标正在使用的字符类型访问,复制的所有位源到目的地而不考虑被复制的数据的类型。

C99更改了语义,因此如果将具有有效类型的对象复制到没有声明类型的存储器(通常是从malloc或其他此类函数接收到的存储器),它将在目标存储器中创建一个只能访问的对象使用源类型。

例如,以下代码将在C89上具有完全定义的行为 “unsigned int”和“unsigned long”具有相同的32位表示但在C99下具有未定义行为的所有平台。

#include <stdio.h> 
#include <string.h> 
void increment_32_bit_uint(void *p) 
{ 
    uint32_t temp; 
    memcpy(&temp, p, sizeof temp); 
    temp++; 
    memcpy(p, &temp, sizeof temp); 
} 
int main(void) 
{ 
    unsigned *ip = malloc(sizeof (unsigned)); 
    unsigned long *lp = malloc(sizeof (unsigned long)); 
    *ip = 3; *lp = 6; 
    increment_32_bit_uint(ip); 
    increment_32_bit_uint(lp);  
    printf("%u %lu", *ip, *lp); 
    return 0; 
} 

根据C99的规则,通过分配存储到“increment_32_bit_uint”功能将使其设置有效的类型来uint32_t的,不能是同一类型的两个“签名”和“无符号长”,即使所有三个类型具有相同的表示形式。因此,即使该类型具有相同的表示形式,编译器也可以使用它读取该存储的代码来执行任何类似于uint32_t的类型的代码。

在C99或C11中,是否有任何方式执行副本,使得编译器能够生成高效的代码,但会强制编译器将目标视为包含一个没有有效的类型[因此可以使用任何类型访问]?

+0

海合会包括''和'' xvan

+2

@xvan后编译使用没有警告'-std = c99'或'-std = c11'您的例子:一个特定的编译器(或者甚至每一个编译器目前存在)恰巧做了一些事情并不意味着标准强制要求继续这样做。有几种情况几乎每个编译器都存在数十年没有标准要求它们这样做的相同行为,直到一些编译器作者认为他们不再需要支持这些情况,所以代码在当今所有编译器并不意味着它不会调用UB。 – supercat

+1

@xvan:Per N1570:“如果使用memcpy或memmove将值复制到没有声明类型的对象中,或者将其复制为字符类型的数组,则该访问和后续访问的修改对象的有效类型不修改该值的值是从中复制该值的对象的有效类型,如果它有一个。“我没有看到说有效类型不会被设置为“uint32_t”,也没有任何其他类型的读取不会调用未定义行为。 – supercat

回答

0

如果你只是使用函数的返回类型,你可以摆脱所有有效的类型问题。

uint32_t increment_32_bit_uint (const void* p) 
{ 
    u32_t result; 
    memcpy(&result, p, sizeof(result)); 
    result++; 
    return result; 
} 

这将强制呼叫者小心他们的类型。虽然理论上这是一种不变的对象,而不是变量的原地变化。但在实践中,我想你会从中得到最有效的代码,无论如何,如果你把它作为

x = increment_32_bit_uint(&x); 

一般情况下,我没有看到任何如何严格别名优化将永远是有用真实世界的应用程序,如果它们不将stdint.h类型视为与其基本数据类型等效的兼容类型。特别是,它必须将uint8_t视为字符类型,否则所有专业低级C代码都会中断。

这里的情况相同。如果编译器知道unsigned int是32位,为什么它会决定为uint32_t的用户造成别名问题,反之亦然?这就是你如何将编译器变成无用的

+0

虽然在本例中我使用了单个数据项,但是数组出现了更大的问题[例如,编写一个函数来减少数组中的每个项目都不是零],所以使用函数的返回值将不起作用。另外,如果你使用在线编译器在例如gcc.godbolt.org,你会注意到即使“int”和“long”都是32位,如果一个函数同时接受'int *'和'long *',编译器会认为这两个指针不能访问同一个对象。一个程序有一些库需要一些'unsigned',有些期望... – supercat

+0

...一个'unsigned long'数组,以及一些需要'uint32_t'数组的函数,以及该程序需要在这些库之间交换数据。还有很多情况下,程序可能需要处理指向共享初始序列的各种结构的指针;然而,今天的编译器不允许这样做,除非使用'memcpy',即使使用'memcpy',有效的类型规则似乎也不能保证它可以工作。 – supercat

+0

@supercat然后我想唯一的选择是使用一个联合,与类型双向/从一个字符类型。 – Lundin