Приведение типа для указателя на функцию qsort

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

    static int cmpstringp(const void *p1, const void *p2)
    {
       /* The actual arguments to this function are "pointers to
          pointers to char", but strcmp(3) arguments are "pointers
          to char", hence the following cast plus dereference */

        return strcmp(* (char * const *) p1, * (char * const *) p2);
    }

    int main(int argc, char *argv[])
    {
        int j;

        assert(argc > 1);

        qsort(&argv[1], argc - 1, sizeof(argv[1]), cmpstringp);

        for (j = 1; j < argc; j++)
            puts(argv[j]);
        exit(EXIT_SUCCESS);
    }

Я смущен этой частью:

        return strcmp(* (char * const *) p1, * (char * const *) p2);

Почему они это сделали? Почему НЕ сделали этого: (const char**) или (const char * const*)? Разве мы не получим указатель на const char, если один раз разыменуем (const char**)? Разыменовав второй, мы не получим указатель const, указывающий на const char. Оба они кажутся тем, что запрашивает strcmp(): два указателя, которые указывают на константные символы. То, что справочная страница, кажется, дает нам константные указатели, указывающие на неконстантные вещи, что, похоже, не то, о чем просит объявление strcmp(). Даже если это законно, не кажется хорошей идеей давать функции что-то, что не соответствует ее параметрам. Я что-то пропустил?

Наконец, почему следующее не генерирует хотя бы предупреждение об ошибке:

    auto const char * const ptr3 = *(const char **) ptr1; //where ptr1 is    
    of the form int foo(const void * ptr).

Разыменование ptr1 один раз дает нам указатель на константный char, но сам по себе не является константой. Однако ptr3 является константой. Так почему же компилятор не генерирует предупреждение? Я что-то упустил, или есть причина, по которой не должно выдаваться предупреждение?


person user678392    schedule 21.10.2011    source источник
comment
Забудьте, что auto — это ключевое слово в C, или не используйте его. C++11 определил его использование, но в C99 это шум.   -  person Jonathan Leffler    schedule 22.10.2011


Ответы (2)


Сначала последний вопрос:

Это хорошо:

const void *ptr = ...;
const char * const ptr3 = *(const char **) ptr1;
^^^^^^^^^^^^ ~~~~~          ^^^^^^^^^^^^
     |                            |
     +----------------------------+

Здесь я могу упростить это с помощью typedef:

typedef const char * string;
const void *ptr = ...;
const string ptr3 = *(string *) ptr;
~~~~~ ^^^^^^          ^^^^^^
        |                |
        +----------------+

const в локальной переменной (той, что подчеркнута ~~~~~) на самом деле не нужна: она указывает, что локальная переменная ptr3 равна const, а не что данные, на которые она указывает, являются константами.

Следующий вопрос: Почему (или почему бы и нет) не *(const char * const *) ptr1 ?

Что ж, тип *(const char * const *) ptr1 — это const char * const или const string, если вы используете typedef. Но он используется только как rvalue. Нет никакой разницы между константным и неконстантным значением rvalue. Например, посмотрите на следующий код:

void *ptr = ...;
int x = *(const int *) ptr;
int y = *(int *) ptr;

Очевидно, что x и y получают одно и то же значение. Таким образом, нет никакой реальной семантической выгоды от добавления const здесь, это просто лишний набор текста.

Но: некоторые компиляторы выдают предупреждение...

const void *ptr = ...;
string ptr2 = *(string *) ptr;

Поскольку вы приводите указатель на const void к указателю на неконстантный string, некоторые компиляторы могут выдать предупреждение о том, что вы отбрасываете квалификаторы. Компиляторы C++ не должны даже пропускать такой код, так как они потребуют const_cast для преобразования из const void * в string * (он же const char *const *).

Однако обратное преобразование работает нормально. Можно передавать указатели на неконстантные объекты в strcmp. Тот факт, что strcmp принимает указатели на константные данные, указывает на то, что сам strcmp не изменяет данные.

Есть много хороших способов написать функцию. Вот несколько примеров.

return strcmp(*(char **) p1, *(char **) p2);
return strcmp(*(const char **) p1, *(const char **) p2);
return strcmp(*(char * const *) p1, *(char * const *) p2);
return strcmp(*(const char * const *) p1, *(const char * const *) p2);

// The following are not technically portable, but are portable in practice
return strcmp(*(void **) p1, *(void **) p2);
return strcmp(*(const void **) p1, *(const void **) p2);
return strcmp(*(void * const *) p1, *(void * const *) p2);
return strcmp(*(const void * const *) p1, *(const void * const *) p2);

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

Последний совет. Ключевое слово auto устарело. В наши дни это ничего не значит — это спецификатор класса хранения по умолчанию. Не используйте auto.

person Dietrich Epp    schedule 22.10.2011
comment
Спасибо за исчерпывающий ответ. Тем не менее, есть одна вещь, которую я не могу уложить в голове. Вы сказали: поскольку вы приводите указатель на const void к указателю на неконстантную строку, некоторые компиляторы могут выдать предупреждение о том, что вы отбрасываете квалификаторы. Что меня смущает, так это то, что const void *ptr - это указатель void, указывающий на константные данные. Но (string *) — это то же самое, что (const char *), который является указателем на константные данные. Зачем компилятору жаловаться на это? Звучит так, как будто указатель void является константным указателем, указывающим на константные данные. Но это не может быть правильным. Так почему актерский состав? - person user678392; 23.10.2011
comment
Нет, string * не то же самое, что const char *, это то же самое, что const char **. Вот почему я использовал typedef. Если вы возьмете const void *, который является указателем на константу, и приведете его к string *, который является указателем на неконстанту, то некоторые компиляторы будут жаловаться. - person Dietrich Epp; 23.10.2011

Первая часть вашего вопроса, я думаю, что есть некоторая гибкость в том, где вы размещаете const, поэтому const char ** и char * const * одинаковы.

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

person Mark Ransom    schedule 22.10.2011
comment
Существует некоторая гибкость, но const char ** (указатель на указатель на const char) эквивалентен только char const **, тогда как char *const * (указатель на const указатель на char) отличается. - person Dietrich Epp; 22.10.2011