【问题标题】:qsort seems to change valuesqsort 似乎改变了值
【发布时间】:2012-10-10 10:36:57
【问题描述】:

我遇到了以下问题:

int sort_compare(const void *x, const void *y) {
    const int *xi = *(const int **) x;
    const int *yi = *(const int **) y;

    for (int i = 0; i < block_size; i++) {
        comp_x[i] = block[(*xi + i) % block_size];
        comp_y[i] = block[(*yi + i) % block_size];
    }

    return memcmp(comp_x, comp_y, block_size);
}

void sort() {
    for (int i = 0; i < block_size; i++) {
        printf("%d,", to_sort[i]);
    }
    puts("");
    qsort(to_sort, block_size, sizeof (int), sort_compare);
    for (int i = 0; i < block_size; i++) {
        printf("%d,", to_sort[i]);
    }
    puts("");
}

values:
    block_size = 5;
    block = "jkldj";
    comp_x, compy_y and to_sort are well allocated

output:
    0,1,2,3,4,
    3,0,1785357420,1,1684826986,

to_sort 包含来自(圆形)字符串的第一个字母,例如

qwer
werq
erqw
rqwe

,表示为(0,1,2,3)需要排序到

erqw
rqwe
qwer
werq

,表示为 (2,3,0,1)。我似乎得到了非常大的数字,这是为什么呢?

提前致谢!

【问题讨论】:

  • 我很惊讶当您尝试取消引用 xiyi 时它不会崩溃;你是在 32 位还是 64 位机器上?
  • 'q' 出现在 'r' 之前,因此您的示例中的结果应该是 (2, 0, 3, 1)
  • @AdamRosenfield 是的,这也让我感到惊讶。它按预期在我的(64 位)盒子上崩溃了。
  • 当被视为 little-endian 32 位数字时,数组中的两个奇怪值会解码为“ldjj”和“jkld”。这些都是comp_xcomp_y 的可能值,这引起了我的怀疑,即您在某处浪费内存。由于我在上面的代码中没有看到它,它可能在你没有发布的代码中。

标签: c qsort


【解决方案1】:

传入比较器的xy 是指向数组元素的指针。您的数组元素是ints,因此要获得int 值,您需要将void 指针转换为int 指针并取消引用。你的代码中有一个额外的间接层,它应该看起来像这样:

int xi = *(const int *) x;
int yi = *(const int *) y;

那么直接使用xiyi 代替*xi*yi 进行数据比较。

作为一种优化,不需要将数据复制到单独的数组中,然后memcmp 它们——你可以自己在循环中比较它们:

for (int i = 0; i < block_size; i++) {
    char data_x = block[(xi + i) % block_size];
    char data_y = block[(yi + i) % block_size];
    if (data_x != data_y)
        return data_x - data_y;
}

return 0;

作为进一步的优化,如果您将block 数组中的数据加倍(例如,使其具有"qwerqwer" 而不仅仅是"qwer"),您可以在一次调用memcmp 中进行比较,因为您不再需要处理环绕。 memcmp 进行了大量优化,因此如果您有大量数据,使用memcmp 会比手写的 for 循环快得多。

【讨论】:

  • +1,很好的优化技巧。不过,我不确定它是否能解决 OP 的问题。
【解决方案2】:

初始化时

const int *xi = *(const int **) x;
const int *yi = *(const int **) y;

to_sort 元素的地址被解释为const int**,然后取消引用以提供xiyi 的值。这会将to_sort 中的值(如果int*s 大于ints 则可能超出)中的值解释为指针。

你应该只投void*s:

const int *xi = (const int *) x;
const int *yi = (const int *) y;

【讨论】:

  • 你尝试过什么“两者”?它在什么方面仍然不起作用?
  • 我怀疑最后一部分是原因。是的,它是 UB,但由于实际上只有 comp_x[i] 被写入,to_sort 不太可能在那里被覆盖。
【解决方案3】:

qsort() 通过给它一个 N 项的线性列表来唱歌,其中任何给定的 item(n) 地址都可以使用基于地址 + 每个“item”的大小来计算。所以从一些简单的东西开始,我所说的简单是指一个指针列表。

首先,可以通过简单地将副本拼接到原始缓冲区来模拟缓冲区的循环性(理想情况下少一个字符,但我不会对一个字节争论不休)。 IE。

"qwer" ==> "qwerqwer"

这可以通过以下方式完成:

char *buff = malloc(2 * blocksize);
memcpy(buff, to_sort, blocksize);
memcpy(buff+blocksize, to_sort, blocksize);

现在您有了偏移量 0..(blocksize-1),每个偏移量都是一个块大小的字符,可以在没有任何特殊指针数学运算的情况下相互比较。

接下来,构建指针列表以进行实际排序,在这种情况下,

char** ptrs = malloc(sizeof(char*) * blocksize); 
for (i=0;i<blocksize;i++)
    ptrs[i] = buff+i;

接下来,一个比较两个“项目”的函数。我们通过地址传递给我们的项目是指向字符串的指针。同样,过去作为左右的地址是内存位置,我们将找到两个字符 *。地址本身是 not char *:

int block_compare(const void *left, const void *right)
{
    // memcmp would work for most platforms, but not all, so...
    return strncmp(*(char **)left, *(char **)right, blocksize);
}

最后,将这个发送到 qsort() 如下:

qsort(ptrs, blocksize, sizeof(char*), block_compare);

最终的输出将是一个块大小的指针列表,指向制造的循环缓冲区,每个指针都引用一个块大小的块。上述所有内容的全文如下:

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

size_t blocksize = 0;

int block_compare(const void *left, const void *right)
{
    // memcmp would work for most platforms, but not all, so...
    return strncmp(*(char **)left, *(char **)right, blocksize);
}


int main(int argc, char* argv[])
{
    char to_sort[] = "qwer";
    size_t i = 0;

    // set blockize
    blocksize = strlen(to_sort);

    char *buff = malloc(2 * blocksize);
    memcpy(buff, to_sort, blocksize);
    memcpy(buff+blocksize, to_sort, blocksize);

    char ** ptrs = malloc(blocksize * sizeof(char*));
    for (i=0;i<blocksize;++i)
        ptrs[i] = buff+i;

    // now send the pointer list to qsort()
    qsort(ptrs, blocksize, sizeof(*ptrs), block_compare);

    // ptrs is sorted. do with it what you will.
    for (i=0;i<blocksize;i++)
    {
        fwrite(ptrs[i], sizeof(char), blocksize, stdout);
        fwrite("\n", sizeof(char), 1, stdout);
    }
    fflush(stdout);

    free(ptrs);
    free(buff);

    return EXIT_SUCCESS;
}

使用“qwer”产生:

erqw
qwer
rqwe
werq

另一个示例,使用“asubstantiallylongerstringtest”

allylongerstringtestasubstanti
antiallylongerstringtestasubst
asubstantiallylongerstringtest
bstantiallylongerstringtestasu
erstringtestasubstantiallylong
estasubstantiallylongerstringt
gerstringtestasubstantiallylon
gtestasubstantiallylongerstrin
iallylongerstringtestasubstant
ingtestasubstantiallylongerstr
llylongerstringtestasubstantia
longerstringtestasubstantially
lylongerstringtestasubstantial
ngerstringtestasubstantiallylo
ngtestasubstantiallylongerstri
ntiallylongerstringtestasubsta
ongerstringtestasubstantiallyl
ringtestasubstantiallylongerst
rstringtestasubstantiallylonge
stantiallylongerstringtestasub
stasubstantiallylongerstringte
stringtestasubstantiallylonger
substantiallylongerstringtesta
tantiallylongerstringtestasubs
tasubstantiallylongerstringtes
testasubstantiallylongerstring
tiallylongerstringtestasubstan
tringtestasubstantiallylongers
ubstantiallylongerstringtestas
ylongerstringtestasubstantiall

伙计,我希望这就是你要找的。 (呼)。

【讨论】:

  • @Jens 在 C#-minor 中,我认为 =P
猜你喜欢
  • 2020-10-15
  • 2017-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-10
  • 1970-01-01
  • 2021-01-25
  • 2022-01-20
相关资源
最近更新 更多