Неопределенное поведение при передаче указателя на функцию с формальным параметром указателя на const? - PullRequest
3 голосов
/ 25 февраля 2012

Я работал над кодом, похожим на следующий:

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, так и указатель. Разве это плохая практика кодирования, чтобы не приводить тип в вышеуказанной ситуации?

Спасибо за вашу помощь!

Ответы [ 4 ]

4 голосов
/ 25 февраля 2012

Это абсолютно нормально;компилятор выполняет неявное преобразование из 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 за их комментарии, разъясняющие последнюю ситуацию. В предыдущей версии этого ответа я ошибочно утверждал, чточто будет неопределенным поведением.)


Отредактировано, чтобы добавить: Обратное - определение функции с объявлением с указателем на неконстантныйпараметр, но вызов его с помощью объявления с параметром указателя на const - также разрешен по той же причине;но попытка на самом деле изменить данные вполне может привести к неопределенному поведению, в зависимости от того, откуда оно.(Например, A char * может инициализировать строковую константу, но попытка изменить данные в этой константе является неопределенным поведением.)

2 голосов
/ 25 февраля 2012

Преобразование типов в константу или из вещей - это то, что я считаю плохой практикой - обычно нет ничего плохого в передаче неконстантного указателя в функцию, ожидающую константного.

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

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

1 голос
/ 25 февраля 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).

1 голос
/ 25 февраля 2012

Это работает для меня, и я не думаю, что параметр указателя 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);
}
...