*(unsigned char *)s1和(unsigned char)* s1之间的C差异

时间:2013-11-20 20:50:51

标签: c pointers syntax casting

我有一个重新编写libc中可用的流行C函数的任务。

我正在写strcmp,当我完成并对它感到满意时,我去检查了libc中的那个。

这是我的:

int     ft_strcmp(const char *s1, const char *s2)
{
    while (*s1 && *s1 == *s2)
    {
        s1++;
        s2++;
    }
    return ((unsigned char)*s1 - (unsigned char)*s2);
}

这是libc中的一个(https://www.opensource.apple.com/source/Libc/Libc-262/ppc/gen/strcmp.c):

int
strcmp(const char *s1, const char *s2)
{
    for ( ; *s1 == *s2; s1++, s2++)
    if (*s1 == '\0')
        return 0;
    return ((*(unsigned char *)s1 < *(unsigned char *)s2) ? -1 : +1); // HERE ! Why *(unsigned char *) :/ ?
}

我不明白为什么*(unsigned char *)s1有效,我认为不会,但它似乎真的如此!

然后我在另一个libc(https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=a4645638eb685e479b89a5e3912076329cc27773;hb=HEAD

中找到了这个实现
int
strcmp (p1, p2)
  const char *p1;
  const char *p2;
{
    const unsigned char *s1 = (const unsigned char *) p1;
    const unsigned char *s2 = (const unsigned char *) p2;
    unsigned char c1, c2; 
    do
    {
       c1 = (unsigned char) *s1++;
       c2 = (unsigned char) *s2++;
       if (c1 == '\0')
           return c1 - c2;
     }
     while (c1 == c2); 
     return c1 - c2;
}

这也很奇怪,但出于其他原因,这个使用了我认为正确的(const unsigned char *) p1

4 个答案:

答案 0 :(得分:3)

(unsigned char *)s1const char *s1(unsigned char *)s1*(unsigned char *)s1的类型转换s1取消引用它以获取值。

答案 1 :(得分:3)

您获取char*并将其取消引用至char,然后将其转换为unsigned char

你认为不会起作用的那个只是首先将指针强制转换为unsigned char*,然后当它取消引用时它是unsigned char

在这种情况下因为它只是从charunsigned char,所以基本上没有区别。

如果原始指针指向int或其他内容,则您的int会将unsigned char投射到int。另一个将获得unsigned char的第一个字节并将其作为{{1}}

返回

答案 2 :(得分:1)

(unsigned char)*s1*(unsigned char*)s1之间的区别在于数据从s1指向的位置加载的方式:

  • (unsigned char)*s1读取s1类型的值,然后将该值转换为unsigned char。此变体无法调用未定义的行为。

    如果s1double*,则会读取double(也就是说,将从内存中加载8个字节),其值将转换为{{1} }}

  • unsigned char首先更改指针应指向的内容,然后读取位于*(unsigned char*)s1处的第一个字节。在某些情况下,这是使用较新标准的未定义行为,但是您的情况不会调用未定义的行为。

    如果s1再次为s1,则生成的代码将在存储double的第一个字节中加载位模式(即,只加载一个字节)。这将与double的逻辑值完全不同。


Asside

关于未定义行为的可能性,规则大致如下:

  • 将指针强制转换为“足够接近”的东西。这包括改变常量和签名的演员表。

  • 强制转换为double*类型是一种特殊情况,它们从不调用未定义的行为。 (感谢Jens Gustedt指出这一点。)

所以我们有以下几种情况:

  • char*投放到int*即可。

  • const unsigned int*投放到int*即可。

  • char*转换为double*来分析double的位模式是未定义的行为,并允许编译器插入格式化硬盘的代码。

答案 3 :(得分:1)

简短回答:charunsigned char相似,可以用同样的方式解释。

答案很长:C标准足够具体,它保证charunsigned char的大小都是1字节,并以相同的格式存储它们的“值位”。因此,最多值为127,严格定义此函数的行为。

当你到达标志位时,它只会变得混乱。 C标准允许符号位表示一个补码,两个补码或有符号幅度,具体取决于实现。因此,在使用二进制补码的平台上(到目前为止最常见),-1将表示为11111111,并且在解释为unsigned char时将等于255。但是使用带符号的幅度,当被解释为10000001时,它将表示为unsigned char并且等于129。

在后一种情况下,这与显式转换为unsigned char(unsigned char) s1++示例)所得到的不同:

  

如果新类型是无符号的,则通过重复添加或转换该值   减去一个可以在新类型中表示的最大值   直到该值在新类型的范围内。

因此,C标准保证,如果您明确将-1投射到unsigned char,则会添加值256,从而生成投射255的结果。因此,如果您使用的是签名幅度的平台:

    char c = -1;
    unsigned char u1 = (unsigned char)c; // this results in 255
    unsigned char u2 = *(unsigned char *)&c; // this results in 129!

我认为这些差异是如此罕见,以至于没有人注意到它们。不使用2的补码的C实现很少而且很远。