Почему следующая программа не работает?
void test(int*[]);
int main()
{
char *a[2];
test((int*[])a);
return 0;
}
void test(int* c[]){
}
Это показывает
ошибка приведения к неполному типу 'int*[]' test((int*[])a)
Проблему можно устранить, используя:
test((int**)a);
Но что не так с предыдущим кодом?
Обновлено: в объявлении прототипа функции внутри круглых скобок используется то же самое, что и приведение.
(целое*[])
Так почему же это не неполный тип?
Это неправильно. Запускается следующая программа: void test(int*); int main() { char* a; тест(а); вернуть 0; } void test(int* c){ }
Эта программа неправильно сформирована и должна генерировать диагностику. Кроме того, он принципиально отличается от этого. Там вы пытаетесь преобразовать char *
в int *
; в этом вопросе вы пытаетесь интерпретировать char *
как int *
Я не уверен, что приведение всего массива за раз правильно, попробуйте приводить каждый член отдельно.
«Что не так с предыдущим кодом?» --> int*[]
— неполный тип, не подходящий для кастинга.
Это неправильно. Запускается следующая программа — это бессмысленно. Тот факт, что код компилируется и выполняется, не означает, что он корректен — это просто означает, что синтаксис работает. Вы можете скомпилировать и запустить ужасный код, содержащий логические ошибки и перезапись памяти, и даже вам повезет, и он проработает минуту, час, неделю или год. Это не меняет того факта, что это неправильно.
Конечно. Но где синтаксическая ошибка? Вот что я хотел знать. В приведенном выше объявлении прототипа функции я использовал то же самое, что и приведение (int*[]). Так почему же это не неполный тип?
В списке параметров функции типы массивов (на верхнем уровне) настраиваются на типы указателей.
@Cryogen Нет синтаксической ошибки, есть ошибка ограничения, означающая, что вы нарушаете обязательные правила языка C.
Обработка массива char
как массива int
также является строгим нарушением псевдонимов и сама по себе вызывает неопределенное поведение, а также может вызывать неопределенное поведение, если int
имеет более строгие требования к выравниванию, чем char
.
Проблема в том, что int *[]
— это неполный тип, экземпляр которого невозможно создать.
В объявлении функции int *[]
в точности эквивалентен int **
, поскольку фактический аргумент, передаваемый функции, в любом случае будет «указателем на указатель на int». Следовательно, в контексте объявления функции int *[]
фактически относится к полному типу (указатель на указатель на int).
Однако в приведении (int *[])a
сообщает компилятору создать временный тип int *[]
, а это недопустимо, поскольку это неполный тип.
Хотя мы не можем иметь значения неполного типа, мы можем иметь указатели на такие значения, поэтому компилируется следующее (только ради примера; это не рекомендуемая конструкция) — для более четкой записи используется typedef:
typedef int * incompleteIntArray[];
void test(int*[]);
int main()
{
char *a[2];
test(*(incompleteIntArray *)&a);
}
Дело в том, что хотя incompleteIntArray
(он же int *[]
) является неполным типом, указатель на этот тип им не является. Следовательно, указатель &a
на char *[2]
можно привести к указателю на int *[]
, и мы можем разыменовать его, чтобы создать аргумент функции. Однако это работает только потому, что массивы всегда передаются по ссылке, поэтому фактический аргумент — это указатель, а не экземпляр int *[]
.
Проблема не в этом. Проблема в том, что приведение типов массива не допускается.
@Lundin Я думаю, это вопрос точки зрения. Сообщение компилятора OP совершенно ясно, но мой компилятор также жалуется на приведение типа массива, как вы говорите. Независимо от проблемы с приведением типа массива, существует проблема неполного типа, на которой я решил сосредоточиться.
C просто не позволяет приводить типы массивов. Вероятно, по той же причине, что и не позволяет присваивать типы массивов. Просто язык указан.
C17 6.5.4 Операторы приведения:
Ограничения
Если имя типа не указывает недействительный тип, имя типа должно указывать атомарный, квалифицированный или неквалифицированный скалярный тип, а операнд должен иметь скалярный тип.
Скаляр означает «одну переменную», в отличие от «агрегата», который представляет собой массивы или структуры. Поэтому приведение структуры к другому типу структуры также не допускается.
В некоторых ситуациях вы можете преобразовать указатель на массив или указатель на структуру в указатель на другой массив или структуру, но это также почти всегда неверная практика и во многих случаях вызывает неопределенное поведение.
Причина, по которой (int**)a
компилируется, заключается в том, что параметр массива в функции всегда неявно подстраивается под указатель на ее первый элемент. Таким образом, в случае void test(int*[]);
это на 100% эквивалентно void test(int**);
, потому что первый элемент — это int*
, а указатель на него будет int**
.
Но то, что ваш код компилируется, не означает, что он правильный. C допускает всевозможные дикие и безумные преобразования указателей, но приведение символьного указателя к указателю к целочисленному указателю по-прежнему приводит к недопустимому преобразованию. Поскольку эти типы несовместимы, и разыменование результирующего указателя приведет к неопределенным ошибкам поведения.
Конкретное предупреждение о том, что int*[]
является неполным типом, здесь менее актуально, поскольку в любом случае код совершенно неверен. Массив без указания размера является неполным типом. В случае функции это не имеет значения, поскольку массив в любом случае будет скорректирован, как упоминалось ранее. Но в другом месте кода, вероятно, не имеет смысла создавать экземпляр типа неполного массива, если C вообще это позволяет. А это не так, поскольку приведение типов массивов недопустимо.
int *
иchar *
— разные типы. Компилятор диагностирует это. Если вы попытаетесь скрыть ошибку с помощью приведения, поведение во время выполнения будет неопределенным.