[英]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進行比較。比較運算符(實際上,所有算術運算符)首先對其操作數執行整數提升,這將轉換為char
到int
。 據我所知,陷阱表示的整數提升是未定義的行為,因此在假設的機器上運行的假設C實現中,您需要使用unsigned char
來實現strcpy
。
使用
char *
是安全的還是我需要unsigned char *
?
也許
“字符串處理”函數(如memcpy()
具有以下規范:
對於本子條款中的所有函數,每個字符都應解釋為它具有
unsigned char
類型(因此每個可能的對象表示都是有效的並且具有不同的值)。 C11dr§7.23.13
使用unsigned char
是指定的“as if”類型。 嘗試其他人很少 - 這可能會或可能不會奏效。
將char
與memcpy()
可能有效,但將該范例擴展到其他類似函數會導致問題。
一個大的理由,以避免char
的str...()
和mem...()
一樣的功能是,有時它使一個功能上的差異意外。
memcmp(), strcmp()
肯定與( signed ) char
和unsigned char
。
迂腐:在帶有簽名 char
relic non-2補碼上,只有'\\0'
應該以字符串結尾 。 但是negative_zero == 0
和帶有negative_zero
的char
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
具有陷阱值的情況下進行確認實現最終會導致矛盾:
fread()
和fwrite()
fgets()
將char *
作為其第一個參數,可以用於二進制文件。 strlen()
從給定的char *
查找到下一個null的距離。 由於fgets()
保證寫入一個,因此它不會讀取超過數組的末尾,因此不會陷阱 不需要 unsigned
,但沒有理由使用plain char
來實現此功能。 普通char
只應用於實際字符串。 對於其他用途, unsigned char
或uint8_t
和int8_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.