簡體   English   中英

實現`memcpy()`:需要`unsigned char *`,還是`char *`?

[英]Implement `memcpy()`: Is `unsigned char *` needed, or just `char *`?

我正在實現一個memcpy()版本,以便能夠與volatile一起使用它。 使用char *是安全的還是我需要unsigned char *

volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n)
{
    const volatile char *src_c  = (const volatile char *)src;
    volatile char *dest_c       = (volatile char *)dest;

    for (size_t i = 0; i < n; i++) {
        dest_c[i]   = src_c[i];
    }

    return  dest;
}

我認為如果緩沖區的任何單元格中的數據都是> INT8_MAX (我認為可能是UB),則必須使用unsigned來避免溢出問題。

理論上,您的代碼可能運行在禁止簽名char一個位模式的計算機上。 它可能使用負整數的補碼或符號幅度表示,其中一個位模式將被解釋為帶負號的0。 即使在二進制補碼架構上,該標准允許實現限制負整數的范圍,以便INT_MIN == -INT_MAX ,盡管我不知道任何實際的機器是這樣做的。

因此,根據§6.2.6.2p2,可能有一個簽名字符值,實現可能將其視為陷阱表示:

這些[負整數的表示]中的哪一個適用於實現定義,如符號位為1且所有值位為零的值(對於前兩個[符號幅度和二進制補碼]),還是符號位和所有值值位1(對於“補碼”)是陷阱表示或正常值。 在符號和幅度以及1'補碼的情況下,如果該表示是正常值,則稱為負零

(字符類型不能有任何其他陷阱值,因為§6.2.6.2要求signed char沒有任何填充位,這是唯一可以形成陷阱表示的方法。出於同樣的原因,沒有位模式是unsigned char的陷阱表示。)

因此,如果這個假設的機器有一個C實現,其中char被簽名,那么通過char復制任意字節可能涉及復制陷阱表示。

對於除char之外的有符號整數類型(如果它恰好是簽名的)和signed char ,讀取作為陷阱表示的值是未定義的行為。 但是§6.2.6.1/ 5只允許為字符類型讀取和寫入這些值:

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

(第三句有點笨拙,但為了簡化:將值存儲到內存中是“修改所有對象的副作用”,因此也允許這樣做。)

簡而言之,由於該異常,您可以在memcpy的實現中使用char而不必擔心未定義的行為。

然而, strcpy也是如此。 strcpy必須檢查終止字符串的尾隨NUL字節,這意味着它需要將它從內存中讀取的值與0進行比較。比較運算符(實際上,所有算術運算符)首先對其操作數執行整數提升,這將轉換為charint 據我所知,陷阱表示的整數提升是未定義的行為,因此在假設的機器上運行的假設C實現中,您需要使用unsigned char來實現strcpy

使用char *是安全的還是我需要unsigned char *

也許


“字符串處理”函數(如memcpy()具有以下規范:

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

使用unsigned char是指定的“as if”類型。 嘗試其他人很少 - 這可能會或可能不會奏效。


charmemcpy() 可能有效,但將該范例擴展到其他類似函數會導致問題。

一個大的理由,以避免charstr...()mem...()一樣的功能是,有時它使一個功能上的差異意外。

memcmp(), strcmp()肯定與( signedcharunsigned char

迂腐:在帶有簽名 char relic non-2補碼上,只有'\\0'應該以字符串結尾 但是negative_zero == 0和帶有negative_zerochar negative_zero應該表示字符串的結尾

你不需要未unsigned

像這樣:

volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n)
{
    const volatile char *src_c  = (const volatile char *)src;
    volatile char *dest_c       = (volatile char *)dest;

    for (size_t i = 0; i < n; i++) {
        dest_c[i]   = src_c[i];
    }

    return  dest;
}

試圖在char具有陷阱值的情況下進行確認實現最終會導致矛盾:

  • fopen(“”,“rb”)不需要只使用fread()fwrite()
  • fgets()char *作為其第一個參數,可以用於二進制文件。
  • strlen()從給定的char *查找到下一個null的距離。 由於fgets()保證寫入一個,因此它不會讀取超過數組的末尾,因此不會陷阱

不需要 unsigned ,但沒有理由使用plain char來實現此功能。 普通char只應用於實際字符串。 對於其他用途, unsigned charuint8_tint8_t類型更精確,因為明確指定了signedness。

如果要簡化功能代碼,可以刪除強制轉換:

volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n) {
    const volatile unsigned char *src_c = src;
    volatile unsigned char *dest_c = dest;

    for (size_t i = 0; i < n; i++) {
        dest_c[i] = src_c[i];
    }
    return dest;
}

暫無
暫無

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

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