【问题标题】:how to make a qsort function and sort array of pointers to structs如何创建一个 qsort 函数并对指向结构的指针数组进行排序
【发布时间】:2020-11-09 03:22:51
【问题描述】:

我正在尝试从头开始创建一个 qsort 函数,该函数对指向结构的指针数组进行排序

这是我现在的代码

static void swap(int *a, int *b) {
  int tmp = *a;
  *a = *b;
  *b = tmp;
}

void _qsort(void* list, int list_len, int left, int right, 
            int(*comp)(const struct shpg_item *a, const struct shpg_item *b)) {
  void *vt, *v3; 
  int i, last, mid = (left + right) / 2; 
  if (left >= right) 
    return; 

  void* vl = (char*)(list + (left * list_len)); 
  void* vr = (char*)(list + (mid * list_len)); 
  swap(vl, vr); 
  last = left; 
  for (i = left + 1; i <= right; i++) { 

    // vl and vt will have the starting address  
    // of the elements which will be passed to  
    // comp function. 
    vt = (char*)(list + (i * list_len)); 
    if ((*comp)(vl, vt) > 0) { 
      ++last; 
      v3 = (char*)(list + (last * list_len)); 
      swap(vt, v3); 
    } 
  } 
  v3 = (char*)(list + (last * list_len)); 
  swap(vl, v3); 
  _qsort(list,list_len, left, last - 1, comp);
  trace_int(1);
  _qsort(list, list_len, last + 1, right, comp); 
}

void list_sort(struct shpg_item **list, int list_len,
               int(*comp)(const struct shpg_item *a, const struct shpg_item *b)) {
  _qsort(*list,list_len,0,(list_len-1),comp);
}

但这给出了分段错误错误,谁能告诉我为什么并帮助我?

【问题讨论】:

  • 或许可以尝试模仿qsort的签名正是
  • @n.'pronouns'm。那会是什么?我在网上找遍了,但找不到类似的实现
  • 你不是从实现开始的。您从文档开始。标准qsort 函数的签名 是什么?将其用于您自己的功能。
  • 我无法理解您的算法,例如您在任何情况下都使用swap(vl, vr); swap(vl, v3); ,所以如果不先检查元素的顺序是否错误,那是没有意义的。此外,list + (left * list_len) 和其他类似的表达式是一个很好的方法,可以以未定义的行为(您的分段错误错误)退出数组,为什么要乘以 list_length
  • 您的swap 函数交换了两个ints。但是由qsort 排序的数组不是ints 的数组。因此,将其视为列表数组是未定义行为。 (除了提到的其他问题之外。)

标签: arrays c sorting struct quicksort


【解决方案1】:

我没有检查整个代码,但您的交换功能似乎有误。取决于代码中的注释行;

// vl and vt will have the starting address  
// of the elements which will be passed to  
// comp function. 

如果 (list + (left * list_len))(list + (last * list_len)) 是要交换的指针(例如指向字符串或结构的指针),则交换函数装饰和调用者行应为:

  1. 交换两个整数、浮点数、双精度值等(通常仅交换值):
void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}
...
int x = 5;
int y = 3;
swap(&x, &y);
  1. 如果您需要交换两个指针(char * 字符串或指向结构的其他类型的指针),您可以只交换指针值而不交换实际内存中指向的内容:
void swap(void **a, void **b) {
    void *t = *a;
    *a = *b;
    *b = t;
}
...
char *x = "some string";
char *y = "some other string";
swap(&x, &y);

【讨论】:

  • swap() 用于指向结构的指针肯定不会工作,因为struct * 的大小与void * 的大小不同。而是使用 swap() 和使用 struct some_struct * 而不是 void * 来实现“对指向结构的指针数组进行排序”和可移植性。
  • @rcgldr C 允许void * 的大小与struct * 不同。 C17dr § 6.2.5 28. 但相当罕见。为什么认为总是一样的:经验?规格?
  • @rcgldr Nether 有 I。我所看到的指针大小的唯一差异是函数指针与对象指针 - 在 2020 年很常见。我怀疑 void *struct *int * 的差异等是为了允许各种早期记忆模型。
  • @chux-ReinstateMonica - 我已经检查过 C 标准、C89、C99。对于 32 位环境,所有指针大小和 size_t 都是 32 位,对于 64 位环境,所有指针大小和 size_t 都是 64 位。在传统的 16 位模式中,有近指针(16 位)和远指针(32 位:段和偏移量,或者在 286 保护模式下,选择器和偏移量)。
  • @rcgldr 您的发现反映了各种实现的一小部分样本,而不是 C 规范。建议评论Are all data pointers the same size in one platform for all data types?
【解决方案2】:

void *指针加法

void * 指针添加是未定义的行为。但是由于通常的UB是可以的,这可能是也可能不是OP的麻烦。

void _qsort(void* list, int list_len, int left, ...
    ...
    (list + (left * list_len))  // UB

建议在添加之前进行强制转换。

// void* vl = (char*)(list + (left * list_len)); 
void* vl = ((char*) list) + (left * list_len); 

可能存在其他问题

【讨论】:

  • 我不确定代码中是否存在任何其他问题,但您是正确的!指针值(list + (left * list_len)) 不会被单独评估,因为_qsort 函数将参数list 作为void * 获取,长度未知。如果您在答案中添加调用程序(&amp;(((char*)list) + (left * list_len))))和对交换函数装饰的更正,我将删除我的。
  • @ssd 让我们在这里等待 OP 一段时间,看看有什么附加内容。希望minimal reproducible example
  • @chux-ReinstateMonica - 我在答案的中间部分添加了一个工作示例(使用 void**)。
【解决方案3】:

我在这个答案的中间部分包含了一个工作示例,并且还添加了一个使用 qsort 的示例。

快速查看我在这里看到问题的代码:

void _qsort(void* list, ...

既然列表是一个指针数组,它应该是:

void _qsort(void** list, ...

void _qsort(void* list[], ...

有了这个声明,指针运算就不会成为问题,例如,list+3 == &list[3] == 指向数组中第三个指针的指针。不需要强制转换列表,因为 void** list 在代码的主要部分可以正常工作。唯一可以进行任何转换的代码是调用者的比较函数。

你可以选择使用type void **来模拟qsort的比较函数参数:compare(list+i, list+j),但是使用type void *会更简单:compare(list[i], list[j ])。

Swap 应该使用 void** 作为参数。电话将是

    swap(list+i, list+j)

/*   ... */

void swap(void **i, void **j){
void * t;
    t = *i;
    *i = *j;
    *j = t;
}

有一些关于 void 指针的 cmets 可能具有与结构指针或任何类型的数据指针不同的大小,这可能会导致问题。如果这是真的,那么 C 库函数 qsort() 将不起作用,因为 qsort 的第一个参数是一个 void 指针,这将导致调用者的指针被强制转换为一个 void 指针。在调用者的比较函数中,两个参数都是 const void 指针,调用者的比较函数必须将其转换为实际的指针类型。使用 qsort() 和调用者的比较函数,参数可以毫无问题地与 void 指针相互转换。

C 保证 void 指针可用于保存任何类型的数据指针,因此本质上 void 指针是通用数据指针(在 16 位段或选择器环境中,通用“近”数据指针)。


这是一个工作示例,使用典型的 Lomuto 分区方案 (pivot = a[hi]):

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int  data;
    char name[32];
}XMPL;

int cmpr(void * pi, void *pj)
{
    if(((XMPL *)pi)->data < ((XMPL *)pj)->data)
        return -1;
    if(((XMPL *)pi)->data > ((XMPL *)pj)->data)
        return  1;
    return 0;
}

void swap(void **i, void **j){
void * t;
    t = *i;
    *i = *j;
    *j = t;
}

void QuickSort(void **a, int lo, int hi, int(*cmpp)(void *, void *))
{
void *p;
int i, j;

    while(lo < hi){
        p = a[hi];
        i = lo;
        for(j = lo; j < hi; ++j){
            if((cmpp(a[j], p) < 0)){
                swap(a+i, a+j);
                ++i;
            }
        }
        swap(a+i, a+hi);
        if(i - lo <= hi - i){           /* avoid stack overflow */
            QuickSort(a, lo, i-1, cmpp);
            lo = i+1;
        } else {
            QuickSort(a, i+1, hi, cmpp);
            hi = i-1;
        }
    }
}

#define COUNT (1024)

int main(int argc, char**argv)
{
XMPL *ax;                               /* array of structures */
XMPL **pax;                             /* array of pointers to structures */
int i;

    ax =  malloc(COUNT * sizeof(XMPL));
    pax = malloc(COUNT * sizeof(void **));

    for(i = 0; i < COUNT; i++){         /* init structs, array of ptrs */
        ax[i].data = rand();
        pax[i] = ax+i;
    }

    QuickSort(pax, 0, COUNT-1, cmpr);

    for(i = 1; i < COUNT; i++){
        if(pax[i-1]->data > pax[i]->data){
            break;
        }
    }
    if(i == COUNT)
        printf("passed\n");
    else
        printf("failed\n");
    
    free(pax);
    free(ax);

    return(0);
}

Hoare 分区方案可能会快一点。但是,在这种情况下,归并排序应该比快速排序更快。与快速排序相比,合并排序的移动次数更多,但比较次数更少,在这种情况下,只有指针被移动,而比较涉及通过指针的间接寻址和通过指针对比较函数的调用。


相同的基本代码,但使用 qsort。请注意,cmpr() 函数需要对每个参数再取消引用。

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int  data;
    char name[32];
}XMPL;

int cmpr(const void * pi, const void *pj)
{
    if((*(XMPL **)pi)->data < (*(XMPL **)pj)->data)
        return -1;
    if((*(XMPL **)pi)->data > (*(XMPL **)pj)->data)
        return  1;
    return 0;
}

#define COUNT (1024)

int main(int argc, char**argv)
{
XMPL *ax;                               /* array of structures */
XMPL **pax;                             /* array of pointers to structures */
int i;

    ax =  malloc(COUNT * sizeof(XMPL));
    pax = malloc(COUNT * sizeof(void **));

    for(i = 0; i < COUNT; i++){         /* init structs, array of ptrs */
        ax[i].data = rand();
        pax[i] = ax+i;
    }

    qsort(pax, COUNT, sizeof(XMPL *), cmpr);

    for(i = 1; i < COUNT; i++){
        if(pax[i-1]->data > pax[i]->data){
            break;
        }
    }
    if(i == COUNT)
        printf("passed\n");
    else
        printf("failed\n");
    
    free(pax);
    free(ax);

    return(0);
}

【讨论】:

  • @AnttiHaapala - 是的,void * 的指针运算未定义,但 void ** 的指针运算已定义并且可以正常工作。
  • 但是 OP 不是排序 void * 而是一个结构指针数组...
  • 再次 Chux 已经 proved that it was wrong......
  • 次要:想在pax = malloc(COUNT * sizeof(void **)); 中使用void **。建议的替代方案:pax = malloc(COUNT * sizeof *pax);
  • @chux-ReinstateMonica - 通常我会使用 sizeof(*pax) 或 sizeof(pax[0]),但重点是使用 type (void **) 作为通用类型一个指针数组。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-13
  • 2014-07-04
  • 2021-08-02
  • 2019-01-18
  • 1970-01-01
  • 2021-06-20
相关资源
最近更新 更多