我有一个重新编写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
答案 0 :(得分:3)
(unsigned char *)s1
从const char *s1
到(unsigned char *)s1
和*(unsigned char *)s1
的类型转换s1取消引用它以获取值。
答案 1 :(得分:3)
您获取char*
并将其取消引用至char
,然后将其转换为unsigned char
你认为不会起作用的那个只是首先将指针强制转换为unsigned char*
,然后当它取消引用时它是unsigned char
。
在这种情况下因为它只是从char
到unsigned char
,所以基本上没有区别。
如果原始指针指向int
或其他内容,则您的int
会将unsigned char
投射到int
。另一个将获得unsigned char
的第一个字节并将其作为{{1}}
答案 2 :(得分:1)
(unsigned char)*s1
和*(unsigned char*)s1
之间的区别在于数据从s1
指向的位置加载的方式:
(unsigned char)*s1
读取s1
类型的值,然后将该值转换为unsigned char
。此变体无法调用未定义的行为。
如果s1
是double*
,则会读取double
(也就是说,将从内存中加载8个字节),其值将转换为{{1} }}
unsigned char
首先更改指针应指向的内容,然后读取位于*(unsigned char*)s1
处的第一个字节。在某些情况下,这是使用较新标准的未定义行为,但是您的情况不会调用未定义的行为。
如果s1
再次为s1
,则生成的代码将在存储double的第一个字节中加载位模式(即,只加载一个字节)。这将与double的逻辑值完全不同。
关于未定义行为的可能性,规则大致如下:
将指针强制转换为“足够接近”的东西。这包括改变常量和签名的演员表。
强制转换为double*
类型是一种特殊情况,它们从不调用未定义的行为。 (感谢Jens Gustedt指出这一点。)
所以我们有以下几种情况:
将char*
投放到int*
即可。
将const unsigned int*
投放到int*
即可。
将char*
转换为double*
来分析double的位模式是未定义的行为,并允许编译器插入格式化硬盘的代码。
答案 3 :(得分:1)
简短回答:char
和unsigned char
相似,可以用同样的方式解释。
答案很长:C标准足够具体,它保证char
和unsigned 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实现很少而且很远。