Typecast за указател на функция 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**)? Дереферирайки втория, не получаваме ли константен указател, който сочи към константен char. И двете изглеждат като това, което strcmp() иска: два указателя, които сочат към const chars. Това, което 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 е const. Така че защо компилаторът не генерира предупреждение? Пропускам ли нещо или има причина да не генерира предупреждение?


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. Няма разлика между константна и неконстантна rстойност. Например, погледнете следния код:

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 взема указатели към const данни, показва, че самият 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 е празен указател, сочещ към const данни. Но (низ *) е същото като (const char *), което е указател към const данни. Защо един компилатор би се оплаквал от това? Звучи така, сякаш void указателят е const указател, сочещ към const данни. Но това не може да е правилно. Така че защо актьорският състав? - person user678392; 23.10.2011
comment
Не, string * не е същото като const char *, то е същото като const char **. Ето защо използвах typedef. Ако вземете const void *, което е указател към const, и го прехвърлите към string *, което е указател към non-const, тогава някои компилатори ще се оплакват. - 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