Typecast для указателя функции qsort - PullRequest
1 голос
/ 21 октября 2011
    #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**)?Разыменовывая второй, мы не получаем константный указатель, который указывает на константный символ.Оба из них, кажется, strcmp() запрашивают: два указателя, которые указывают на константные символы.То, что man-страница, кажется, дает нам константные указатели, указывающие на неконстантные вещи, что, похоже, не то, о чем просит объявление strcmp().Даже если это допустимо, не очень хорошая идея давать функции то, что не соответствует ее параметрам.Я что-то пропустил?

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

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

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

Ответы [ 2 ]

2 голосов
/ 22 октября 2011

Последний вопрос первый:

Это нормально:

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. Но он используется только в качестве значения. Нет разницы между константным и неконстантным значением. Например, посмотрите на следующий код:

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 * (a.k.a 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.

0 голосов
/ 22 октября 2011

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

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

...