我正在实现一个版本的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;
}
我认为unsigned
应该是必要的,以避免溢出问题,如果缓冲区的任何单元格中的数据是> INT8_MAX
,我认为可能是UB。
从理论上讲,您的代码可能运行在一台禁止签名的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只允许为字符类型读取和写入这些值:
某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示并且由不具有字符类型的左值表达式读取,则行为是未定义的。如果这样的表示是由副作用产生的,该副作用通过不具有字符类型的左值表达式修改对象的全部或任何部分,则行为是未定义的。这种表示称为陷阱表示。 (重点补充)
(第三句有点笨拙,但为了简化:将值存储到内存中是“修改所有对象的副作用”,因此也允许这样做。)
简而言之,由于这个例外,你可以在char
的实现中使用memcpy
,而不必担心未定义的行为。
然而,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()
肯定与(签名)char
与unsigned char
不同。
迂腐:在签名char
的遗物非2的补语中,只有'\0'
应该结束一个字符串。然而negative_zero == 0
和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;
}
尝试确认qazxsw poi具有陷阱价值的实施最终会导致矛盾:
char
和fread()
fwrite()
将fgets()
作为其第一个参数,可用于二进制文件。char *
从给定的strlen()
中找到到下一个null的距离。由于char *
保证有一个写入,它不会读取超过数组的末尾,因此不会陷阱不需要fgets()
,但没有理由使用普通的unsigned
来实现此功能。普通的char
只能用于实际的字符串。对于其他用途,char
或unsigned char
和uint8_t
类型更精确,因为明确指定了签名。
如果要简化功能代码,可以删除强制转换:
int8_t