实现`memcpy()`:需要`unsigned char *`,还是`char *`?

时间:2019-03-02 23:56:24

标签: c pointers casting char unsigned

我正在实现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(我认为可能是UB),我认为应该使用> INT8_MAX以避免溢出问题。

4 个答案:

答案 0 :(得分:3)

从理论上讲,您的代码可能在禁止签名的char中使用一位模式的计算机上运行。它可能使用负整数的补码或正负号表示,其中一个位模式将被解释为带负号的0。即使在二进制补码体系结构上,该标准也允许实现将负整数的范围限制为INT_MIN == -INT_MAX,尽管我不知道有任何实际的机器这样做。

因此,根据§6.2.6.2p2,可能有一个带符号的字符值,实现可以将其视为陷阱表示形式:

  

[负整数的表示形式]中的哪些适用于实现定义,符号位为1且所有值位为零的值(对于前两个[符号幅度和二进制补码])还是带符号位和所有值位1(表示1的补码)是陷阱表示形式或正常值。如果是正负号,幅度和一的补数,则如果此表示形式是正常值,则称为负零

(字符类型不能有其他任何陷阱值,因为§6.2.6.2要求signed char不能有任何填充位,这是可以形成陷阱表示形式的唯一其他方法。原因,没有位模式是unsigned char的陷阱表示。)

因此,如果此假设机器具有其中对char进行签名的C实现,则通过char复制任意字节可能涉及复制陷阱表示。

对于char(如果碰巧是有符号的)和signed char以外的有符号整数类型,读取作为陷阱表示形式的值是未定义的行为。但是§6.2.6.1/ 5允许仅针对字符类型读取和写入这些值

  

某些对象表示不必表示对象类型的值。如果对象的存储值具有这种表示形式,并且由不具有字符类型的左值表达式读取,则该行为是不确定的。如果这样的表示是由副作用产生的,该副作用通过不具有字符类型的左值表达式修改对象的全部或任何部分,则该行为是不确定的。这样的表示称为陷阱表示。 (添加了强调)

(第三句话有点笨拙,但为简化起见:将值存储到内存中是“修改所有对象的副作用”,因此也是允许的。)

简而言之,由于这个异常,您可以在char的实现中使用memcpy,而不必担心不确定的行为。

但是,strcpy并非如此。 strcpy必须检查结尾的NUL字节,该字节终止一个字符串,这意味着它需要将从内存中读取的值与0进行比较。比较运算符(实际上是所有算术运算符)首先对其操作数执行整数提升,这会将char转换为int。据我所知,陷阱表示的整数提升是不确定的行为,因此在假设计算机上运行的假设C实现中,您需要使用unsigned char才能实现strcpy

答案 1 :(得分:2)

  

使用char *是否安全?我是否需要unsigned char *

也许


诸如memcpy()之类的“字符串处理”函数具有以下规范:

  

对于本节中的所有功能,每个字符都应被解释为具有unsigned char类型(因此,每个可能的对象表示形式都是有效的并且具有不同的值)。 C11dr§7.23.13

使用unsigned char是指定的“好像”类型。尝试他人几乎无济于事-可能成功也可能无法成功。


charmemcpy()一起使用可以,但是将这种范式扩展到其他类似函数会导致问题。

charstr...()之类的函数避免使用mem...()的一个重要原因是有时它会意外地使 functional 有所不同。

memcmp(), strcmp()charunsigned char之间肯定是( signed ){}。

书呆子:在带有 signed char的遗迹非2的补语上,只有'\0'应该以 string 结尾。然而,negative_zero == 0和带有char的{​​{1}}也不应表示 string 的结尾。

答案 2 :(得分:1)

您不需要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()被保证已经写了一个,因此它将不会读取到数组末尾,因此不会陷阱

答案 3 :(得分:1)

不需要unsigned,但没有理由为此功能使用普通char。普通char仅应用于实际字符串。对于其他用途,类型unsigned charuint8_tint8_t更精确,因为已明确指定了签名。

如果要简化功能代码,可以删除强制转换:

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;
}