Что означает этот код в C qsort?

void qsort (void *a, size_t n, size_t es, int (*compare)(const void *, const void *)

где a — начало адреса массива, n — sizeof массива, es — sizeof элемента массива.

Я прочитал исходный код qsort на C, который я не могу понять. код выглядит следующим образом.

#define SWAPINT(a,es) swaptype = ((char*)a- (char*)0 % sizeof(long) || \
        es % sizeof(long) ? 2: es == sizeof(long)? 0 : 1

Я интерпретирую этот макрос,

if(((char*)a- (char*)0)% sizeof(long))==1 || es % sizeof(long)==1)
     swaptype = 2;
else if(es== sizeof(long))
     swaptype = 0;
else
     swaptype = 1;

Но я не понимаю, зачем реализовано преобразование типов, (char*)a.

И что значит эта строка?

(char*)a- (char*)0)% sizeof(long)==1

person mathcom    schedule 03.10.2016    source источник
comment
Этот код кажется довольно сломанным. В макросе есть по крайней мере одна синтаксическая ошибка, отсутствующая скобка. Кроме того, (char*) a - (char*) 0 не должно быть операции, если я что-то упустил? Как следует (char*) 0 % sizeof(long).   -  person jforberg    schedule 03.10.2016
comment
(char*)0 % sizeof(long) даже не имеет смысла, потому что тип указателя не является арифметическим типом. Что бы это ни было, это не соответствует C. Где вы нашли этот код? А вы уверены, что правильно скопировали?   -  person    schedule 03.10.2016
comment
% лучше -, поэтому (char*)a- (char*)0 % sizeof(long) равно (char*)a - ((char*)0 % sizeof(long)). Конечно, ((char*)a- (char*)0) % sizeof(long) хотелось.   -  person chux - Reinstate Monica    schedule 03.10.2016
comment
==1 в вашей интерпретации должно быть !=0. Также отсутствует ) после (char*)0 в вашем макросе SWAPINT, что приводит к несбалансированным скобкам. Макрос SWAPINT, по-видимому, устанавливает swaptype = 2, если a не выровнено (по границе sizeof(long) байтов) или es не кратно sizeof(long), устанавливает swaptype = 0, если a выровнено, а es равно точно sizeof(long), или устанавливает swaptype = 1, если a выровнено, а es является целым числом. несколько != 1 из sizeof(long).   -  person Ian Abbott    schedule 03.10.2016
comment
@IanAbbott Хорошо, я думаю, это все. У вас есть какие-нибудь идеи, почему они делают (a - 0), конечно, расчет без операции?   -  person jforberg    schedule 03.10.2016
comment
((char*)a - (char*)0) преобразует a в целочисленное значение типа ptrdiff_t без явного приведения. Он не является переносимым, но поскольку он является частью реализации стандартной библиотеки для платформы, он не должен быть переносимым.   -  person Ian Abbott    schedule 03.10.2016


Ответы (3)


Где бы вы ни нашли этот код, вы, вероятно, скопировали его неправильно. Я нашел очень похожий код в libutil от Canu:

c.swaptype = ((char *)a - (char *)0) % sizeof(long) || \
  es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;

Этот код, вероятно, был незаконно (поскольку условия авторского права нарушены) скопирован из libc FreeBSD:

//__FBSDID("$FreeBSD: src/lib/libc/stdlib/qsort.c,v 1.12 2002/09/10 02:04:49 wollman Exp $");

Итак, я предполагаю, что вы получили его из реализации *BSD libc. Реализация быстрой сортировки Indeedd FreeBSD содержит элемент SWAPINIT макрос (не SWAPINT):

#define SWAPINIT(TYPE, a, es) swaptype_ ## TYPE =       \
        ((char *)a - (char *)0) % sizeof(TYPE) ||       \
        es % sizeof(TYPE) ? 2 : es == sizeof(TYPE) ? 0 : 1;

После синтаксического анализа вы должны обнаружить, что приведенный выше код примерно такой же, как

condition_one   = ((char *)a - (char *)0) % sizeof(long);
condition_two   = es %  sizeof(long);
condition_three = es == sizeof(long);
c.swaptype = (condition_one || condition_two) ? 2 : condition_three ? 0 : 1;

Обратите внимание, что condition_two в качестве условия не совпадает с es % sizeof(long) == 1, а скорее с es % sizeof(long) != 0. Кроме того, ваш перевод был правильным.


Цель этих условий выглядит следующим образом:

  • condition_one равно true, когда a не выровнено по long.
  • condition_two равно true, когда es не кратно long.
  • condition_three равно true, когда es равно long.

Как результат,

  • swaptype == 2 - это когда у вас недостаточно гарантий относительно элементов, чтобы умничать при замене,
  • swaptype == 1 предназначен для массивов с элементами, выровненными по long границам (примечание: но не обязательно выровненными по longs!), и
  • swaptype == 0 предназначен для массивов, соответствующих предыдущему описанию, которые также имеют элементы размера long.

В этом случае происходит явное преобразование типов, поскольку a имеет тип void*, для которого арифметика типов не определена. Однако также обратите внимание, что ((char *)a - (char *)0) тоже не определено:

Когда два указателя вычитаются, оба должны указывать на элементы одного и того же объекта массива или один после последнего элемента объекта массива; результатом является разница индексов двух элементов массива.

(Черновик C11 N1570, раздел 6.5.6. , пункт 9 на стр. 93 и 94.)

Это не совсем точно указано в C11, но нулевой указатель не является частью того же массива, что и объект, на который указывает a, поэтому основные правила арифметики указателей нарушаются, поэтому поведение не определено.

person Community    schedule 03.10.2016

Макросы пытаются переносимо проверить выравнивание на языке C, который на самом деле не позволяет проводить такой тест. Итак, мы вычитаем нулевой указатель из нашего указателя, чтобы получить целое число, а затем берем модуль размером с тип long. Если результат равен нулю, данные выровнены по длине, и мы можем получить доступ как longs. Если это не так, мы можем попробовать какую-то другую схему.

person Malcolm McLean    schedule 03.10.2016
comment
Нельзя ли в этом случае использовать _Alignas и _Alignof C11? - person ; 03.10.2016
comment
Даже если он короче, я думаю, что понял этот ответ лучше. Однако что означает это целое число? Это начальный адрес массива? И почему имеет значение, что массив считается длинным? например, если это массив байтов? - person Asoub; 04.10.2016
comment
Когда мы вычитаем два указателя, мы получаем целое число (не int), которое представляет собой количество мест между ними. Вычитание NULL из char * не является операцией, но оно заставляет компилятор принять указатель как целое число и разрешить для него модуль. Это также немного рискованно, если пространство памяти сегментировано, но это уже другая история. - person Malcolm McLean; 04.10.2016

Как отмечено в комментариях, определение макроса, которое вы представляете, не расширяется до действительного кода C, поскольку оно включает вычисление (char*)0 % sizeof(long), где левый операнд % имеет тип char *. Это не целочисленный тип, но оба операнда % должны иметь целочисленный тип.

Кроме того, расширение макроса имеет несбалансированные круглые скобки. Это не является по сути неправильным, но делает этот макрос сложным в использовании. Кроме того, даже там, где приоритет оператора дает разумный результат, использование скобок и дополнительных пробелов может помочь человеку интерпретировать код без ущерба для скорости выполнения и незначительных дополнительных затрат на компиляцию.

Итак, я думаю, что желаемый макрос будет примерно таким:

#define SWAPINT(a,es) swaptype = (                                  \
    ((((char*)a - (char*)0) % sizeof(long)) || (es % sizeof(long))) \
        ? 2                                                         \
        : ((es == sizeof(long)) ? 0 : 1))                           \
)

Вместо этого я бы предпочел написать предпоследнюю строку как

        : (es != sizeof(long))

чтобы уменьшить сложность выражения за счет небольшой потери его понятности. В любом случае намерение состоит в том, чтобы установить swaptype на:

  • 2, если a не выровнено по границе n байт, где n — количество байтов в long, или если es не является целым числом, кратным размеру long; в противном случае
  • 1, если es не равно размеру long; в противном случае
  • 0

Это похоже, но не идентично вашей интерпретации. Обратите внимание, однако, что даже этот код имеет неопределенное поведение из-за (char*)a - (char*)0. Оценка этой разницы определила поведение только в том случае, если оба указателя указывают на один и тот же объект или сразу за его концом, а (char *)0 не указывает (в) на конец какого-либо объекта или сразу за его концом.

Вы спросили конкретно:

Но я не понимаю, зачем реализовано преобразование типов, (char*)a.

Это выполняется, потому что арифметика указателя определяется в терминах типа, на который указывает указатель, поэтому (1) соответствующая программа не может выполнять арифметику с void *, и (2) код хочет, чтобы результат вычитания был в тех же единицах. как результат оператора sizeof (байт).

И что значит эта строка?

(char*)a- (char*)0)% sizeof(long)==1

Эта строка не появляется в макросе, который вы представили, и это не полное выражение из-за несбалансированных скобок. Похоже, он пытается определить, указывает ли a на единицу за границей n байт, где n определено выше, но опять же, оценка разницы указателей имеет неопределенное поведение. Также обратите внимание, что для целого числа x значение x % sizeof(long) == 1, оцениваемое в логическом контексте, имеет другое значение, чем x % sizeof(long), оцениваемое в том же контексте. Последнее имеет больше смысла в контексте, который вы описали.

person John Bollinger    schedule 03.10.2016