Я прочитал раздел Standard N1570 6.5.2.2 Function calls
и был озадачен особым значением типа функции, который включает в себя прототип.Точно 6.5.2.2(p6)
Если функция определена с типом, который не содержит прототип, и типы аргументов после продвижения не совместимы с типами параметров после продвижения, поведениене определено, за исключением следующих случаев:
- один повышенный тип является целочисленным типом со знаком, другой повышенный тип является целочисленным типом без знака, и значение представимо в обоих типах;
- оба типа являются указателями на квалифицированные или неквалифицированные версии символьного типа или void.
6.5.2.2(p7)
предоставляет правило вызова функции с прототипом:
Если выражениеэто означает, что вызываемая функция имеет тип, который включает в себя прототип, аргументы неявно преобразуются, как если бы путем присваивания, в типы соответствующих параметров, принимая тип каждого параметра за неквалифицированную версию объявленного типа.
Рассмотрим следующий пример:
struct test_arg{
int a;
};
void test_no_prototype(const struct test_arg a){ }
void test_with_prototype(const struct test_arg a);
void test_with_prototype(const struct test_arg a){ }
int main(){
struct test_arg test = {.a = 42};
test_no_prototype(test); //1 UB?
test_with_prototype(test); //2 Fine?
}
Я думаю, что 1 - это UB, потому что test_no_prototype
не включает прототип, а test
имеет неквалифицированную версию struct test_arg
, но аргумент имеет тип const struct test_arg
, который несовместим с struct test_arg
из-за разной квалификации.
Я думаю, что 2 - это хорошо, потому что test_with_prototype
включает в себя прототип и простые ограничения присваивания из 6.5.16.1(p1)
позволяют присваивать переменную квалифицированного типа структуры из неквалифицированной версиита же структура.
Это кажется странным, и пока я не могу представить себе причину того, почему мы по-разному относимся к функциям с прототипом и без него.Возможно, я неправильно понял правило ... Если да, то можете ли вы объяснить, что это значит?