簡體   English   中英

memcpy可以用於打字嗎?

[英]Can memcpy be used for type punning?

這是C11標准的引用:

6.5表達式
...

6訪問其存儲值的對象的有效類型是對象的聲明類型(如果有)。 如果通過具有非字符類型的左值的值將值存儲到沒有聲明類型的對象中,則左值的類型將成為該訪問的對象的有效類型以及不修改該值的后續訪問的有效類型儲值。 如果使用memcpymemmove將值復制到沒有聲明類型的對象中,或者將其復制為字符類型數組,則該訪問的修改對象的有效類型以及不修改該值的后續訪問的有效類型是復制值的對象的有效類型(如果有)。 對於沒有聲明類型的對象的所有其他訪問,對象的有效類型只是用於訪問的左值的類型。

7對象的存儲值只能由具有以下類型之一的左值表達式訪問:

- 與對象的有效類型兼容的類型,
- 與對象的有效類型兼容的類型的限定版本,
- 對應於對象的有效類型的有符號或無符號類型的類型,
- 對應於對象有效類型的限定版本的有符號或無符號類型,
- 聚合或聯合類型,其成員中包含上述類型之一(包括遞歸地,子聚合或包含聯合的成員),或者
- 角色類型。

這是否意味着memcpy不能以這種方式用於類型懲罰:

double d = 1234.5678;
uint64_t bits;
memcpy(&bits, &d, sizeof bits);
printf("the representation of %g is %08"PRIX64"\n", d, bits);

為什么它不會給出相同的輸出:

union { double d; uint64_t i; } u;
u.d = 1234.5678;
printf("the representation of %g is %08"PRIX64"\n", d, u.i);

如果我使用我的版本的memcpy使用字符類型怎么辦:

void *my_memcpy(void *dst, const void *src, size_t n) {
    unsigned char *d = dst;
    const unsigned char *s = src;
    for (size_t i = 0; i < n; i++) { d[i] = s[i]; }
    return dst;
}

編輯: EOF評論說, 第6段中關於memcpy()的部分不適用於這種情況,因為uint64_t bits具有聲明的類型。 我同意,但不幸的是,這無助於回答memcpy是否可用於類型懲罰的問題,它只是使第6段與評估上述例子的有效性無關。

這里是另一個使用memcpy進行類型懲罰的嘗試,我認為第6段將對此進行討論:

double d = 1234.5678;
void *p = malloc(sizeof(double));
if (p != NULL) {
    uint64_t *pbits = memcpy(p, &d, sizeof(double));
    uint64_t bits = *pbits;
    printf("the representation of %g is %08"PRIX64"\n", d, bits);
}

假設sizeof(double) == sizeof(uint64_t) ,上面的代碼是否已根據第6和7段定義了行為?


編輯:一些答案指出來自閱讀陷阱表示的未定義行為的可能性。 這是不相關的,因為C標准明確排除了這種可能性:

7.20.1.1精確寬度整數類型

1 typedef name int N _t指定一個有符號整數類型,其寬度為N ,無填充位和二進制補碼表示。 因此, int8_t表示這樣的帶符號整數類型,其寬度恰好為8位。

2 typedef名稱uint N _t指定一個寬度為N且無填充位的無符號整數類型。 因此, uint24_t表示這樣的無符號整數類型,其寬度恰好為24位。

這些類型是可選的。 但是,如果實現提供寬度為8,16,32或64位的整數類型,沒有填充位,並且(對於具有二進制補碼表示的有符號類型),它應定義相應的typedef名稱。

類型uint64_t恰好有64個值位且沒有填充位,因此不能有任何陷阱表示。

有兩種情況需要考慮: memcpy()進入一個具有聲明類型的對象, memcpy()進入一個沒有聲明類型的對象。

在第二種情況下,

double d = 1234.5678;
void *p = malloc(sizeof(double));
assert(p);
uint64_t *pbits = memcpy(p, &d, sizeof(double));
uint64_t bits = *pbits;
printf("the representation of %g is %08"PRIX64"\n", d, bits);

行為確實是未定義的,因為p指向的對象的有效類型將變為double ,並且通過uint64_t類型的左值訪問有效類型為double的對象是未定義的。

另一方面,

double d = 1234.5678;
uint64_t bits;
memcpy(&bits, &d, sizeof bits);
printf("the representation of %g is %08"PRIX64"\n", d, bits);

沒有定義。 C11標准草案n1570:

7.24.1字符串函數約定
3對於本子條款中的所有函數,每個字符都應解釋為它具有unsigned char類型(因此每個可能的對象表示都是有效的並且具有不同的值)。

6.5表達式
7對象的存儲值只能由具有以下類型之一的左值表達式訪問:88)

- 與對象的有效類型兼容的類型,
- 與對象的有效類型兼容的類型的限定版本,
- 對應於對象的有效類型的有符號或無符號類型的類型,
- 對應於對象有效類型的限定版本的有符號或無符號類型,
- 聚合或聯合類型,其成員中包含上述類型之一(包括遞歸地,子聚合或包含聯合的成員),或者
- 角色類型。

腳注88)此列表的目的是指定對象可能或可能不具有別名的情況。

所以memcpy()本身是明確定義的。

由於uint64_t bits 具有聲明的類型 ,因此即使其對象表示從double復制,它也會保留其類型。

正如chqrlie指出的那樣, uint64_t不能有陷阱表示,因此在memcpy()之后訪問bits 不是未定義的,只要sizeof(uint64_t) == sizeof(double) 但是, bits將取決於實現(例如,由於字節序)。

結論 :只要memcpy()的目標確實具有聲明的類型,即不是由[m/c/re]alloc()或等效的分配, memcpy() 可以用於類型懲罰。

你提出了3種方法,它們都與C標准有不同的問題。

  1. 標准庫memcpy

     double d = 1234.5678; uint64_t bits; memcpy(&bits, &d, sizeof bits); printf("the representation of %g is %08"PRIX64"\\n", d, bits); 

    memcpy部分是合法的(在您的實現中提供sizeof(double) == sizeof(uint64_t) ,這不是每個標准保證的):您通過char指針訪問兩個對象。

    printf線不是。 bits表示現在是雙倍的。 它可能是uint64_t的陷阱表示,如6.2.6.1General§5中所定義

    某些對象表示不需要表示對象類型的值。 如果對象的存儲值具有這樣的表示並且由不具有字符類型的左值表達式讀取,則行為是未定義的。 如果這樣的表示是由副作用產生的,該副作用通過不具有字符類型的左值表達式修改對象的全部或任何部分,則行為是未定義的。 這種表示稱為陷阱表示。

    6.2.6.2整數類型明確表示

    對於unsigned char以外的無符號整數類型,對象表示的位應分為兩組:值位和填充位...任何填充位的值都是未指定的。 53

    注53說:

    填充位的某些組合可能會生成陷阱表示,

    如果您知道在您的實現中沒有填充位(仍然從未見過......),則每個表示都是有效值,並且print行再次變為有效。 但它只是依賴實現,並且在一般情況下可能是未定義的行為

  2. 聯盟

     union { double d; uint64_t i; } u; ud = 1234.5678; printf("the representation of %g is %08"PRIX64"\\n", d, ui); 

    聯合的成員不共享共同的子序列,並且您正在訪問不是最后寫入的值的成員。 好的常見實現將給出預期的結果,但是根據標准,它沒有明確定義應該發生什么。 6.5.2.3結構和工會成員§3中的腳注說如果導致與先前案例相同的問題:

    如果用於訪問union對象內容的成員與上次用於在對象中存儲值的成員不同,則將值的對象表示的相應部分重新解釋為新類型中的對象表示形式在6.2.6中描述(一個過程有時稱為“類型雙關語”)。 這可能是陷阱表示。

  3. 自定義memcpy

    您的實現僅執行始終允許的字符訪問。 它與第一種情況完全相同:實現定義。

根據標准明確定義的唯一方法是將double的表示存儲在正確大小的char數組中,然后顯示char數組的字節值:

double d = 1234.5678;
unsigned char bits[sizeof(d)];
memcpy(&bits, &d, sizeof(bits));
printf("the representation of %g is ", d);
for(int i=0; i<sizeof(bits); i++) {
    printf("%02x", (unsigned int) bits[i]);
}
printf("\n");

如果實現對char使用恰好8位,則結果將僅可用。 但它是可見的,因為如果其中一個字節的值大於255,它將顯示超過8個六位數。


以上所有內容僅有效,因為bits具有聲明的類型。 請參閱@ EOF的答案 ,了解為什么分配的對象會有所不同

我讀了第6段的說法,使用memcpy()函數將一系列字節從一個內存位置復制到另一個內存位置可以用於類型懲罰,就像使用具有兩種不同類型的union可以用於類型雙關語一樣。

第一次提到使用memcpy()表示如果它復制指定的字節數,並且當該變量(左值)用於存儲字節時,這些字節將與源目標的變量具有相同的類型。

換句話說,如果你有一個變量double d; 然后,您為此變量(左值)分配一個值,該變量中存儲的數據類型為double類型。 然后,如果您使用memcpy()函數將這些字節復制到另一個內存位置,比如變量uint64_t bits; 這些復制字節的類型仍然是double

然后,如果您通過目標變量(左值)訪問復制的字節,則uint64_t bits; 在該示例中,該數據的類型被視為用於從該目標變量檢索數據字節的左值的類型。 因此,字節被解釋(未轉換但解釋)為目標變量類型,而不是源變量的類型。

通過不同類型訪問字節意味着字節現在被解釋為新類型, 即使字節實際上沒有以任何方式改變

這也是union工作方式。 union不進行任何轉換。 您將字節存儲到一個類型的union成員中,然后通過另一個union成員將相同的字節拉回。 字節是相同的,但字節的解釋取決於用於訪問存儲區的union成員的類型。

我已經看到舊的C源代碼中使用的memcpy()函數通過使用struct member offset和memcpy()函數將struct變量的一部分復制到其他struct變量中來幫助將struct划分為多個部分。

因為memcpy()使用的源位置的類型是存儲在那里的字節的類型,使用union進行懲罰可以遇到的同類問題也適用於以這種方式使用memcpy()作為數據類型的Endianness

要記住的是,無論是使用union還是使用memcpy()方法,復制的字節類型都是源變量的類型,然后當您再次訪問數據時,無論是通過union的不同成員還是通過memcpy()的目標變量,字節被解釋為目標左值的類型。 但是實際的字節不會改變。

改變 - 見下文

雖然我從未觀察到編譯器將非重疊源和目標的memcpy解釋為執行任何不等同於將源的所有字節讀取為字符類型然后將目標的所有字節寫為一個字符類型(意思是如果目標沒有聲明的類型,它將沒有有效的類型),標准的語言將允許鈍的編譯器進行“優化” - 在極少數情況下,編譯器將是能夠識別和利用它們 - 更有可能破壞原本可行的代碼(並且如果標准寫得更好,將會很好地定義)而不是實際提高效率。

至於這是否意味着最好使用memcpy或手動字節復制循環,其目的被充分偽裝成無法識別為“復制字符類型數組”,我不知道。 我認為明智的做法是避免任何人如此遲鈍,以至於認為一個好的編譯器應該在沒有這種混淆的情況下產生虛假代碼,但是由於過去幾年被認為是鈍的行為目前很流行,我不知道是否memcpy將成為破壞代碼競爭的下一個受害者,編譯器幾十年來將其視為“明確定義”。

UPDATE

從6.2開始,GCC有時會忽略memmove操作,即使它們是不同類型的指針,它們也會看到目標和源識別相同的地址。 如果稍后將作為源類型寫入的存儲讀取為目標類型,則gcc將假定后者讀取不能識別與先前寫入相同的存儲。 gcc的這種行為是合理的,因為標准中的語言允許編譯器通過memmove復制有效類型。 目前還不清楚這是否是關於規則的故意解釋memcpy ,但是,考慮到海灣合作委員會也將作出類似的優化在某些情況下,它顯然不會受到標准允許的,例如,當一個類型的工會成員(如64- bit long )被復制到一個臨時的,並從那里復制到具有相同表示的不同類型的成員(例如64位long long )。 如果gcc發現目標將與臨時目錄一點一點地相同,則會省略寫入,因此無法注意到存儲的有效類型已更改。

可能會給出相同的結果,但編譯器不需要保證它。 所以你根本不能依賴它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM