Недефинирано поведение при предаване на указател към функция с формален параметър на указател към const?

Работя върху някакъв код, който е подобен на следния:

typedef struct
{
    unsigned char x;
    unsigned short y;
    unsigned char[NUM_DEFINED_ELSEWHERE];
} My_Struct;

static My_Struct my_useful_struct;   // Variables initialized elsewhere in code.

void myFunction(const My_Struct * p_my_struct)
{
    /* Performs various read-only actions utilizing p_my_struct. */
}

void myOtherFunction(void)
{
    static My_Struct * p_struct = &my_useful_struct;
    myFunction(p_struct);
}

Моят код се компилира без никакви проблеми, но при прегледа ми беше казано, че освен ако не преобразувам p_struct, това може да доведе до недефинирано поведение на определени платформи (т.е. 8051). Въпреки това никога не съм получавал предупреждение на компилатора. Вярно ли е, че непреобразуването на указателя при предаването му на функцията с (const My_Struct *) може да доведе до недефинирано поведение?

Причината, поради която декларирах горната функция с указател към const, беше, че исках да мога да работя както с указател към const, така и с указател. Лоша практика на кодиране ли е да не се въвежда в горната ситуация?

Благодаря за вашата помощ!


person embedded_guy    schedule 25.02.2012    source източник
comment
(педантична забележка: ако това е недефинирано поведение, то е недефинирано на всички платформи, просто така се случва да нямате късмет и то работи на някои)   -  person Flexo    schedule 25.02.2012
comment
Вашият код не трябва да се компилира: static My_Struct p_struct = &my_useful_struct; е незаконен. Само указателите могат да получат адреса на нещо...   -  person Basile Starynkevitch    schedule 25.02.2012
comment
Мисля, че сте объркали myOtherFunction() - вероятно искате p_struct да бъде указател и да премахнете & от следното извикване...   -  person Christoph    schedule 25.02.2012
comment
както каза @Christoph, това не трябва да се компилира   -  person jupp0r    schedule 25.02.2012
comment
@jupp0r: & не е в името на типа, той е в инициализацията и следователно добрия стар оператор за адрес на.   -  person    schedule 25.02.2012
comment
Опа... пропуснах звездичката. Благодаря!   -  person embedded_guy    schedule 25.02.2012
comment
@awoodland: Добро обаждане...   -  person embedded_guy    schedule 25.02.2012
comment
@embedded_guy: примерният код все още е повреден - сега предавате ** на функция, очакваща *   -  person Christoph    schedule 25.02.2012
comment
Всеки тип указател T * е имплицитно конвертируем в T const *, така че няма нужда от преобразуване (и това наистина е напълно логично).   -  person Kerrek SB    schedule 25.02.2012


Отговори (4)


Това е абсолютно добре; компилаторът извършва имплицитно преобразуване от My_Struct * в const My_Struct *. 6.3.2.3 от спецификацията на C99 казва:

За всеки квалификатор q, указател към неq-квалифициран тип може да бъде преобразуван в указател към q-квалифицирана версия на Тип; стойностите, съхранени в оригиналните и преобразуваните указатели, трябва да се сравняват еднакви.

Освен това, дори ако декларирате функцията с две непоследователни декларации, така че един файл да я види декларирана по следния начин:

void myFunction(My_Struct * p_my_struct);

въпреки че всъщност е дефиниран така:

void myFunction(const My_Struct * p_my_struct) { ... }

дори това е позволено от спецификацията, въпреки че компилаторът не знае да извърши имплицитно преобразуване, защото My_Struct * и const My_Struct * имат едно и също представяне (така че преобразуването така или иначе е без операция).

(Благодаря на Кристоф и awoodland за техните коментари, изясняващи последната ситуация. В предишна версия на този отговор погрешно твърдях, че това би било недефинирано поведение.)


Редактирано за добавяне: Обратното дефиниране на функция с декларация с указател към неконстантен параметър, но извикването й с помощта на декларация с указател към константен параметър също е разрешено, за същата причина; но опитът за действително модифициране на данните може да доведе до недефинирано поведение, в зависимост от това откъде идват. (char * може да инициализира константа на низ, например, но е недефинирано поведение да се опитвате да промените данните в тази константа.)

person ruakh    schedule 25.02.2012
comment
всъщност My_Struct * и const My_Struct * са изисквани да бъдат идентични вътрешно (вижте C99 6.2.5 §27), но доколкото знам, сте прав, че двете декларации са технически несъвместими и използват грешната е недефинирано поведение... - person Christoph; 25.02.2012
comment
И така, какво е решението, кастинг преди извикване на функцията? - person jupp0r; 25.02.2012
comment
@Christoph: не, това трябва да е дефинирано поведение, нали? Предвид неправилния прототип. - person jupp0r; 25.02.2012
comment
@jupp0r: Първо -- Кристоф означава, че решението е: използване на правилния прототип. Второ -- като се има предвид неправилният прототип, изрично преобразуване няма да работи, защото ако изрично го преобразувате към const My_Struct *, тогава компилаторът ще иска имплицитно да го преобразува обратно, за да съответства на прототипа! (И това имплицитно преобразуване е незаконно, да не говорим, че е непрепоръчително.) - person ruakh; 25.02.2012
comment
Примерът, който давате като непозволен е изрично разрешен - §6.7.5.3/15 изрично казва: При определянето на съвместимостта на типове и на съставен тип, всеки параметър, деклариран с тип функция или масив се приема като имащ коригирания тип и всеки параметър, деклариран с квалифициран тип, се приема като имащ неквалифицираната версия на своя деклариран тип (C99, нямам документ за C11 под ръка) - person Flexo; 26.02.2012
comment
@awoodland: Ами сега. :-/ Благодаря за корекцията. Ще редактирам отговора си. - person ruakh; 26.02.2012
comment
@ruakh: освен това не виждам къде този въпрос спомена единица за превод.. опитвам се да намеря такава сега..; добре, премахнах моята, можеш да премахнеш и твоята - person Giorgi Moniava; 03.08.2015
comment
@Giorgi: Примерите в тази дискусия, доколкото си спомням, бяха strlen, strcpy и т.н. Това са стандартни библиотечни функции. При липса на твърдение за противното, единственото разумно предположение е, че техните декларации се #include-d в единицата за превод, използвайки подходящите стандартни заглавки. Няма съмнение, че за това беше половината от разговора ви с Кийт Томпсън. - person ruakh; 03.08.2015

Прехвърлянето на константност към или извън нещата е нещо, което считам за лоша практика - обикновено няма нищо лошо в предаването на неконстантен указател във функция, очакваща константен.

Изключение е, ако има някаква причина, поради която данните могат да се променят по време на изпълнението (напр. друга нишка докосва посочените данни) - тогава може да имате проблем, но това не е проблемът, който преобразуването на типове ще предотврати. В този случай трябва да направите вашата логическа нишка безопасна.

Не забравяйте, че ключовата дума const също не може да се използва от компилатора, за да гарантира постоянство, въпреки че може да се използва за откриване на проблеми, при които смятате, че данните не трябва да се променят, но компилаторът очаква, че искате да ги промените... това е повече инструмент за документиране за вас, отколкото нещо друго.

person jheriko    schedule 25.02.2012
comment
const-квалифицирането на указания тип не прави абсолютно никаква гаранция за променливостта на посочения обект - това е чисто синтактично, тъй като добавянето на квалификатор е имплицитно преобразуване, докато премахването на такъв не е... - person Christoph; 25.02.2012
comment
[добавка] освен когато е съчетано с restrict, но дори и тогава само ако обектът действително е достъпен... - person Christoph; 25.02.2012

Първо предполагам, че & в извикването myFunction(&p_struct) е печатна грешка и това, което наистина имахте предвид, е myFunction(p_struct).

static My_Struct * p_struct = &my_useful_struct;
myFunction(p_struct);

Когато подадете p_struct, няма абсолютно никаква причина да прехвърляте указателя p_struct в извикването на функцията. Това е напълно валидно за предаване на указател към T във функция, където параметърът е указател към const T.

В стандарта C това се управлява от ограниченията на оператора за присвояване (C99, 6.5.16.1p1). При извикване на функция на функция, декларирана с прототип, аргументите се преобразуват като чрез присвояване на типа на съответните параметри (C99, 6.5.2.2p7).

person ouah    schedule 25.02.2012
comment
другояче дефинирано поведение ли е? напр. предаване на const char* към функция, нуждаеща се от char*? (ако приемем, че последното така или иначе не променя стойността, където сочи указателят) - person Giorgi Moniava; 15.06.2015
comment
@giorgi предаването на char * към функция, която приема const char *, не е разрешено, има нарушение на ограничението. - person ouah; 15.06.2015
comment
хм.. Разследвах това е някакъв вграден код и дори производителите на устройства използват много неща като предаване на неподписани указатели на char към функции, очакващи указатели на char и обратно - в техните примерни кодове. Ако това е добре за тях, вярвам, че това, което направих в моя горен коментар, също трябва да е добре? Какво е вашето мнение? - person Giorgi Moniava; 15.06.2015
comment
@giorgi, разбира се, имах предвид предаването на const char * към функция, която приема char *, не е разрешено, има нарушение на ограничението. Същото за предаване на signed char * към функция, изискваща unsigned char *; във втория случай е ОК, ако го кастнете, без каст вашата програма не е подходяща програма. - person ouah; 15.06.2015
comment
@ouah: Да, също няма кастинг, но както казах, вграденото устройство, върху което работя, има много примерен код от производители, които правят това ... - person Giorgi Moniava; 15.06.2015
comment
@giorgi и тъй като те не са съобразени програми, някои компилатори ще отхвърлят тези програми. Тези програми са повредени. - person ouah; 15.06.2015
comment
@giorgi, ако искате да разширите тази дискусия, предлагам ви да отворите нов въпрос - person ouah; 15.06.2015

Това работи за мен и не мисля, че параметърът на указателя const дава недефинирано поведение, компилаторът прави имплицитно преобразуване, преди да извика функцията:

typedef struct
{
    unsigned char x;
    unsigned short y;
    unsigned char[NUM_DEFINED_ELSEWHERE];
} My_Struct;

static My_Struct my_useful_struct;   // Variables initialized elsewhere in code.

void myFunction(const My_Struct * p_my_struct)
{
    /* Performs various read-only actions utilizing p_my_struct. */
}

void myOtherFunction(void)
{
    static My_Struct * p_struct = &my_useful_struct;
    myFunction(p_struct);
}
person jupp0r    schedule 25.02.2012