2016-03-11 31 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