C 中的 strcmp 和 void 指针

strcmp and void pointer in C

我写了这个简单的字符串比较代码

    #include<stdio.h>

    void strCmp(char *,char *);

    int main() {
        char* str1 = "hello";
        char* str2 = "hello";
        strCmp(str1,str2);
        return 0;
    }
    void strCmp(char *vp1,char *vp2) {
        int r = strcmp(vp1,vp2);
        printf("result %d",r);
    }

然后为了检查如果我传递 void* 而不是 char* 会发生什么,我重写了这样的代码:

    #include<stdio.h>

    void strCmp(void *,void *);

    int main() {
        char* str1 = "hello";
        char* str2 = "hello";
        strCmp(str1,str2);
        return 0;
    }
    void strCmp(void *vp1,void *vp2) {
        int r = strcmp(vp1,vp2);
        printf("result %d",r);
    }

以上代码已编译并按预期工作!如果我更疯狂地做

    #include<stdio.h>

    void strCmp(void **,void **);

    int main() {
        char** str1 = "helloxx";
        char** str2 = "hellox";
        strCmp(str1,str2);
        return 0;
    }
    void strCmp(void **vp1,void **vp2) {
        int r = strcmp(vp1,vp2);
        printf("result %d",r);
    }

没有失败,结果很好。我想知道 strcmp 的实现是什么,并将 中的代码作为自定义函数复制粘贴到我的代码中,但它会失败,并出现您在使用 void* 而不进行强制转换时所期望的常见错误消息。

你能解释一下上面的实现是如何工作的吗?

嗯,首先,你真的应该

#include <string.h>

其次,强烈建议您使用strncmp()。最后,这就是 C 的工作方式(不同于 C++)。它将执行从 void* 到任何其他指针的隐式转换(这就是为什么例如 malloc() 可以正常工作)。另见 How to interpret section 6.3.2.3 part 7 of the C11 standard? .

至于为什么 libc 中的代码对您不起作用,我不知道。它当然对我有用(在我修复了缺少的类型声明之后)。

之所以有效,是因为 C 几乎总是假定程序员知道它的作用。

即使 str1 和 str2 声明为 char** 它们实际上包含指向 char

的指针

C 自动将指向任何类型的指针转​​换为指向 void

的指针

所以最后,你将 char * 传递给 strcmp,它就可以完成它的工作了。

但是当我编译它时,我收到了警告(即使在添加 #include <string.h> 以正确声明 strcmp 之后):

foo.c(7) : warning C4047: 'initialisation' : 'char **' diffère de 'char [8]' dans les niveaux d'indirection
foo.c(8) : warning C4047: 'initialisation' : 'char **' diffère de 'char [7]' dans les niveaux d'indirection
foo.c(13) : warning C4047: 'fonction' : 'const char *' diffère de 'void **' dans les niveaux d'indirection
foo.c(13) : warning C4024: 'strcmp' : types différents pour le paramètre formel et réel 1
foo.c(13) : warning C4047: 'fonction' : 'const char *' diffère de 'void **' dans les niveaux d'indirection
foo.c(13) : warning C4024: 'strcmp' : types différents pour le paramètre formel et réel 2

英文应该差不多:

foo.c(7) : warning C4047: 'init' : 'char **' and 'char [8]' have different indexation level
foo.c(8) : warning C4047: 'initialisation' : 'char **' and 'char [7]'  have different indexation level
foo.c(13) : warning C4047: 'function' : 'const char *' and 'void **' have different indexation level
foo.c(13) : warning C4024: 'strcmp' : different types for formal and actual parameter 1
foo.c(13) : warning C4047: 'function' : 'const char *' and  'void **' dhave different indexation level
foo.c(13) : warning C4024: 'strcmp' : different types for formal and actual parameter 2

不应忽略这些警告,因为它们会显示代码中的实际问题。

你的问题有两点。

首先是它实际起作用的原因。 二是你的程序是否保证一直有效

第一点的答案是它有效,因为 strcmp 只需要两个指针作为参数。这些指针是 char * 类型的,它们应该包含以空字符结尾的字符(字节)序列的地址。您的不同实现采用了这两个指针,然后将它们转换为各种类型(在第一种情况下转换为 void *,在第二种情况下转换为 char **),然后将它们转换回 char *,然后调用strcmp。它有效,因为在几乎所有真正的 C 实现中,将一种指针类型转换为另一种指针类型不会改变指针的值。因此,指向哪个指针以及您投射和重铸该指针的次数并不重要。请注意,在 C++ 中情况有所不同,因为转换通常会更改实际地址(指针的值)。

第二点是你的程序是否符合C标准。在这种情况下,第一个是正确的,因为 C 标准规定您始终可以将任何指针转换为 void * 并且当您将其转换回原始类型时,您将取回原始指针。但是,这对于转换为其他类型并非如此,因此不能保证在将 char * 转换为 char ** 并返回到 char * 之后,您会得到原始指针。 C语言不保证你最后的程序一定能运行。

更准确地说,这里有两个来自 C99 标准的相关引用:

6.3.2.3 Pointers

1.) A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

...

7.) A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned57) for the pointed-to type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.