Неопределенное поведение при передаче указателя на функцию с формальным параметром указателя на 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
(педантичное примечание: если это поведение undefined, то оно не определено на всех платформах, просто так получилось, что вам не повезло, и на некоторых это работает)   -  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 * имеют одинаковое представление (так что преобразование в любом случае не выполняется).

(Спасибо Christoph и awoodland за их комментарии, разъясняющие последнюю ситуацию. В предыдущей версии этого ответа я ошибочно утверждал, что это будет поведением undefined.)


Отредактировано для добавления: обратное определение функции с помощью объявления с параметром указатель на неконстанту, но вызов ее с использованием объявления с параметром указатель на константу также разрешено, для та же самая причина; но попытка фактически изменить данные вполне может привести к неопределенному поведению, в зависимости от того, откуда они получены. (Например, 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
хм .. Я исследовал это какой-то встроенный код, и даже производители устройств используют много вещей, таких как передача беззнаковых указателей на символы функциям, ожидающим указатели на символы, и наоборот - в своих примерах кодов. Если с ними все в порядке, я считаю, что то, что я сделал в своем комментарии выше, также должно быть в порядке? Каково твое мнение? - 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