【问题标题】:Can I use memcmp along with qsort?我可以将 memcmp 与 qsort 一起使用吗?
【发布时间】:2017-04-12 19:54:31
【问题描述】:

我正在制作 C 动态数组库,有点。请注意,我这样做是为了好玩在我的空闲时间,所以请不要推荐数以百万计的现有库。

我开始实施排序。数组是任意元素大小,定义为struct:

typedef struct {
  //[PRIVATE] Pointer to array data
  void *array;
  //[READONLY] How many elements are in array
  size_t length;
  //[PRIVATE] How many elements can further fit in array (allocated memory)
  size_t size;
  //[PRIVATE] Bytes per element
  size_t elm_size;
} Array;

我最初准备这个是从排序功能开始的:

/** sorts the array using provided comparator method
 * if metod not provided, memcmp is used
 * Comparator signature
 *  int my_comparator ( const void * ptr1, const void * ptr2, size_t type_size );
**/
void array_sort(Array* a, int(*comparator)(const void*, const void*, size_t)) {
    if(comparator == NULL)
        comparator = &memcmp;
    // Sorting algorithm should follow
}

但是我了解到qsort

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

显然,我可以将我的内部数组传递给qsort。我可以这样称呼:

qsort (a->array, a->length, a->elm_size, comparator_callback);

但有一个问题 - qsort 的比较器签名如下:

int (*compar)(const void*,const void*)

memcmp的签名是:

int memcmp ( const void * ptr1, const void * ptr2, size_t type_size );

qsort 的回调中缺少元素大小,这意味着当NULL 作为回调传递时,我不能再拥有通用比较器函数。我可以手动生成最大为 X 字节元素大小的比较器,但这听起来很难看。

我可以将qsort(或其他内置排序)与memcpy 一起使用吗?还是我必须在内置比较器和内置排序功能之间进行选择?

【问题讨论】:

  • 所以请不要推荐现有的百万库。”我笑了
  • 传递给比较函数的指针将是数组指针。您可以将它们转换为 Array,然后使用该结构的长度成员来确定要比较的字节数。
  • qsort 中的元素大小不是意味着您的数组的elm_size 吗?
  • “但是有一个问题 - qsort 的比较器签名读取为:...”这是因为您将比较函数传递给最后一个参数,而不是数组。
  • @RadLexus 是的,当然......也许我不清楚我问的是什么。我的问题是我无法将该大小传递给 memcpy。默认的比较器函数需要知道什么是元素大小——正是因为它接收两个数组指针并且不知道它们的大小。

标签: c arrays memcpy qsort


【解决方案1】:

C11 为您提供了一个(当然是可选的)qsort_s function,旨在处理这种特定情况。它允许您将用户提供的void * 值(上下文指针)从调用代码传递到比较器函数。本例中的比较器回调具有以下签名

int (*compar)(const void *x, const void *y, void *context)

在最简单的情况下,您可以将指向大小值的指针作为上下文传递

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdlib.h>
...

int comparator_callback(const void *x, const void *y, void *context)
{
  size_t elm_size = *(const size_t *) context;
  return memcmp(x, y, elm_size);
}

...
qsort_s(a->array, a->length, a->elm_size, comparator_callback, &a->elm_size);

或者将指向整个数组对象的指针作为上下文传递可能是有意义的。

一些基于 *nix 的实现已经提供类似的 qsort_r function 有一段时间了,尽管它不是标准的。

【讨论】:

  • 我在需要 C99 的家庭作业中使用该库,但我仍然赞成它,因为它通常很有用。
【解决方案2】:

一种非线程安全的方式是使用私有全局变量来传递大小。

static size_t compareSize = 0;

int defaultComparator(const void *p1, const void *p2) {
  return memcmp(p1, p2, compareSize);
}

void array_sort(Array* a, int(*comparator)(const void*, const void*, size_t)) {
    if(comparator == NULL) {
      compareSize = a->elm_size;
      comparator = &defaultComparator;
    }
    // Sorting algorithm should follow
}

你可以通过 make compareSize 线程局部变量 (__thread) 使其成为线程安全的

【讨论】:

  • 我想到了这一点,但这对我来说是一个禁忌,即使我认为我不会将它与任何多线程一起使用。如果这是唯一的选择,我宁愿从 GNU C 库中复制粘贴和编辑 qsort
  • 为什么? C 上的全局变量有时是不可避免的。通过使其内部链接,我认为它没有任何问题。
  • 我对 C 语言不太熟悉,我不知道。您能否添加一些有关如何使用__thread 的细节?便携吗?
  • __thread 是 GCC 扩展,thread_local 在 C11 中。您可以阅读更多信息here
【解决方案3】:

qsort() API 是更简单时代的遗产。应该有一个额外的“环境”指针从qsort() 调用原样传递到每个比较。这将允许您以线程安全的方式传递对象大小和任何其他必要的上下文。

但它不存在。 @BryanChen 的方法是唯一合理的方法。

我写这个答案的主要原因是指出memcmp 会做一些有用的事情的情况很少。没有多少种对象可以按组成unsigned chars 的字典顺序进行比较有意义。

当然,以这种方式比较 structs 是危险的,因为未指定填充字节值。即使比较的相等部分也可能失败。换句话说,

struct foo { int i; };

void bar(void) { 
  struct foo a, b;
  a.i = b.i = 0;
  if (memcmp(&a, &b, sizeof a) == 0) printf("equal!");
}

可能 - 按照 C 标准 - 不打印!

另一个例子:对于像unsigned ints 这样简单的事情,您将获得大端和小端存储顺序的不同排序顺序。

unsigned a = 0x0102;
unsigned b = 0x0201;
printf("%s", memcmp(&a, &b, sizeof a) < 0 ? "Less! : "More!");

将根据运行的机器打印LessMore

确实,我能想象到的唯一可以与memcmp 比较的对象类型是大小相等的无符号字节块。这不是一个非常常见的排序用例。

总而言之,提供memcmp 作为比较函数的库注定容易出错。有人会尝试用它来代替专门的比较,而这实际上是获得所需结果的唯一方法。

【讨论】:

  • 虽然特定排序确实可能取决于平台,但这并不意味着特定排序在 OP 的上下文中很重要。也许 OP 只关心等效元素的分组,而不关心元素的相对位置。本质上,C++ std::unordered_set 中的元素排序方式可能(并且将会)因一个平台/实现与另一个不同,但这并不限制 std::unordered_set 对其预期目的的可用性。
  • @AnT 他说他正在构建一个动态数组库。我认为假设它包含的类型是任意的是合理的。按字典顺序逐字节比较任意类型几乎没有意义。
猜你喜欢
  • 2023-03-25
  • 2020-08-04
  • 2018-10-23
  • 2011-02-22
  • 2021-03-16
  • 2016-11-20
  • 2019-01-25
  • 2011-06-11
  • 2017-02-20
相关资源
最近更新 更多