C语言 将指向void的指针强制转换为指向指向类型A的指针的指针,以及取消引用与将指向void的指针强制转换为类型A:为什么?

qv7cva1a  于 2022-12-03  发布在  其他
关注(0)|答案(3)|浏览(182)

我刚开始学C。任何帮助都是感激不尽的!
我有一个指向结构体的指针数组,我想使用内置的qsort function根据指针指向的结构体中的值对数组进行排序。我正在尝试使用official docs中演示的比较函数。
以下版本失败:

int compare_nodes(const void* a, const void* b){
    const struct ListNode * ptr1 = ((const struct ListNode *) a);
    const struct ListNode * ptr2 = ((const struct ListNode *) b);
    // const struct ListNode * ptr1 = *((const struct ListNode **) a);
    // const struct ListNode * ptr2 = *((const struct ListNode **) b);
    int arg1 = ptr1 -> val;
    int arg2 = ptr2 -> val;

    if(arg1 < arg2) return -1;
    if(arg1 > arg2) return 1;
    return 0;
}

此版本成功:

int compare_nodes(const void* a, const void* b){
    // const struct ListNode * ptr1 = ((const struct ListNode *) a);
    // const struct ListNode * ptr2 = ((const struct ListNode *) b);
    const struct ListNode * ptr1 = *((const struct ListNode **) a);
    const struct ListNode * ptr2 = *((const struct ListNode **) b);
    int arg1 = ptr1 -> val;
    int arg2 = ptr2 -> val;

    if(arg1 < arg2) return -1;
    if(arg1 > arg2) return 1;
    return 0;
}

我不明白这两个版本之间的区别:
1.如果强制转换只告诉编译器如何解释指针指向的地址,那么版本1中的问题是什么?告诉编译器将指向void的指针解释为指向struct ListNode的指针还不够吗?为什么我需要通过强制转换添加一层间接寻址,然后通过解引用删除一层间接寻址?

  1. C的按值传递在这里起作用吗?我自己想不出任何理由。
    我找到了以下关于这个问题的资源。虽然它们似乎解释了这个问题(特别是资源6),但我并不理解它们:
  2. What are the rules for casting pointers in C?
  3. Typecasting of pointers in C
  4. Pointer type casting and dereferencing
  5. What are the rules for casting pointers in C?
  6. What does a C cast really do?
  7. https://cboard.cprogramming.com/c-programming/102056-casting-pointer-pointer.html
    下面是完整的代码:
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>

struct ListNode {
    int val;
    struct ListNode *next;
};

int calc_list_length(struct ListNode * head){
    int target = 0;
    struct ListNode * tmp = head;

    while (tmp)
    {
        target++;
        tmp = tmp -> next;
    }
    
    return target;
}

int compare_nodes(const void* a, const void* b){
    // const struct ListNode * ptr1 = ((const struct ListNode *) a);
    // const struct ListNode * ptr2 = ((const struct ListNode *) b);
    const struct ListNode * ptr1 = *((const struct ListNode **) a);
    const struct ListNode * ptr2 = *((const struct ListNode **) b);
    int arg1 = ptr1 -> val;
    int arg2 = ptr2 -> val;

    if(arg1 < arg2) return -1;
    if(arg1 > arg2) return 1;
    return 0;
}

struct ListNode* sortList(struct ListNode* head){
    if(!head) return NULL;

    int list_length = calc_list_length(head);
    struct ListNode * tmp = head;

    struct ListNode * arr[list_length];
    for (int i = 0; i < list_length; i++)
    {
        arr[i] = tmp;
        tmp = tmp -> next;
    }

    for (int i = 0; i < list_length; i++) {
        printf("%d ", arr[i] -> val);
    }
    printf("\n");
    
    qsort(arr, list_length, sizeof(struct ListNode *), compare_nodes);

    for (int i = 0; i < list_length; i++) {
        printf("%d ", arr[i] -> val);
    }
    printf("\n");
}

int main(){
    // [2,1,4,3]
    struct ListNode node4 = {.val = 3, . next = NULL};
    struct ListNode * ptr4 = &node4;

    struct ListNode node3 = {.val = 4, .next = ptr4};
    struct ListNode * ptr3 = &node3;

    struct ListNode node2 = {.val = 1, .next = ptr3};
    struct ListNode * ptr2 = &node2;

    struct ListNode node1 = {.val = 2, .next = ptr2};
    struct ListNode * ptr1 = &node1;

    sortList(ptr1);

    getchar();
    return 0;    
}

先谢谢你了。希望你能给我指个方向。

wswtfjt7

wswtfjt71#

qsort使用指向运算符&将指针传递给数组元素。
例如,它可以将&arr[0]&arr[1]作为参数传递给比较函数。
由于arr是一个指针数组,其中每个元素都是一个指针,因此根据定义,指向一个元素的指针必须是指向另一个指针的指针。
因此,传递给compare_nodes结构的参数是指向ListNode结构的指针。

w80xi6nr

w80xi6nr2#

函数qsort声明如下

void qsort(void *base, size_t nmemb, size_t size, 
           int (*compar)(const void *, const void *));

这个函数处理void *类型的指针,它将const void *类型的两个指针传递给比较函数,这两个指针指向底层数组的元素。
您声明了一个指针数组

struct ListNode * arr[list_length];

ListNode *类型的元素通过指向它们的指针的引用传递给比较函数。
实际上,函数将ListNode **类型的指针传递给比较函数,这些指针作为const void *类型的指针传递。

const void *p = &arr[i];

其中表达式&arr[i]具有类型ListNode **
当然,函数qsort实际上并不知道数组元素的实际类型。

const void *p = ( char * )base + i * size;

因此,在比较函数中,您需要执行“反向”转换,如

const struct ListNode * ptr1 = *((const struct ListNode **) a);

其中ptr1是原始数组的元素,通过指向它的指针的引用将该元素传递给比较函数。

o2g1uqev

o2g1uqev3#

qsort()函数将指向数组元素的指针传递给比较器函数,而不是直接按值传递数组元素,这是由于qsort()的设计方式所致,核心C语言本身并不需要。
我可以很容易地编写一个排序函数,对指针数组进行排序,使用以下签名:

void my_sort_pointers(const void **array, size_t nmemb, 
                      int (*compar)(const void *, const void *));

并且它接受一个直接传递数组元素(指针)的比较函数,这样你的比较函数就不需要解引用参数来获得要比较的实际值。
然而,我上面的排序函数不能排序,比如说,一个float数组,因为它不能把两个float参数传递给一个应该接受两个指针的比较函数。即使你创建了一个接受两个float的比较函数,你也不能把它传递给上面的函数,因为函数指针类型是不兼容的。即使您以某种方式强制转换了类型,它仍然无法工作,因为函数内部的C代码认为它调用的是一个接受两个指针的函数,却无法调用实际接受两个float的函数,因为函数签名不兼容。
因此,为了能够在这种设计下对不同类型的数组进行排序,我必须有另一个排序函数来对float的数组进行排序:

void my_sort_floats(float *base, size_t nmemb, 
                    int (*compar)(float, float));

另一个排序函数对int的数组进行排序,等等。由于结构体的类型不限,大小也不一样,所以你不可能创建一个函数来处理所有的结构体。即使只支持基本类型,它将需要几种不同的功能。C标准库的设计人员认为,最好是使用一个适用于所有类型的排序函数,通过将指向数组元素的指针传递给比较器函数。
当您了解C语言的局限性时,这种设计非常有意义。(在C++中,可以使用泛型来编写一个通用的排序函数,该函数适用于任何类型,并且具有一个按值传递数组元素的比较器。但C没有泛型。)但是,这是qsort()函数API的设计决定。和排序函数(至少在它们只接受特定类型的情况下)并不一定要以这种方式工作,所以只有通过阅读qsort()的规范才能知道qsort()应该以这种方式使用。

相关问题