Разумеется можно не смотреть, но тогда для strictly conformming программ стандарт сразу запрещает 'void main'. Собственно уже на этом можно было и остановиться.
Доказательство компилятором заранее обречены на провал. К примеру один из известных компиляторов С++ по умолчанию умеет привязывать временные объекты к не константным ссылкам. Делает ли это эту фичу валидной с точки зрения языка? разумеется нет.
Также и здесь. GCC "по умолчанию" тоже много чего позволяет:
"By default, GCC provides some extensions to the C language".
...
"GNU C provides several language features not found in ISO standard C"
Но это не делает все эти extensions и language features допустимыми с точки зрения языка.
Использование "gcc по дефолту" в рассматриваемом контексте ни к чему хорошему не приведет. Есть стандарт языка, есть conforming implementation. Разумеется нужно понимать, что разработчики компиляторов должны поддерживать совместимость с огромным количеством legacy кода и они не могут просто так взять и добавить в 'default' набор все флаги, необходимые для полного соответствия стандарту. Поэтому это нужно делать самостоятельно, но для этого нужно как минимум обратиться к сопроводительной документации. В которой для gcc явно написано как отключить "language features not found in ISO standard C", как указать версию стандарта и т.п. и сделать (или хотя бы попытаться сделать) из gcc conforming implementation.ибо в нём ни о каких опциях gcc не говорится, т.е. используем gcc "по дефолту".
Увы, "синтаксических ошибок" в примере нет. В одно месте есть нарушение constraint(диагностируемое UB) и не диагностируемое UB в другом месте.не смотря на якобы синтаксическую ошибку gcc и множество других компиляторов С сгенерят совершенно законный и рабочий код.
И кстати вот это самое неопределенное поведение в форме концепции, как показывает практика, очень часто вызывает непонимание.
Приблизительное определение:
На самом деле все усугубляет отсутствие диагностики UB на этапе компиляции. Здесь все дело в том что детектирование всех возможных вариантов неопределенного поведения требует колоссальных усилий со стороны разработчиков компилятора. А в некоторых случаях это детектирование и вовсе невозможно. Из-за этого от компилятора и не требуют этой диагностики. Да, разумеется, в некоторых случаях разумеется диагностика требуется стандартом. Это как раз нарушения требований 'shall' и 'shall not' в C, а также в некотором приближении понятие 'ill-formed' в С++ (там отдельные нюансы).3.4.3/1
Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). EXAMPLE: An example of undefined behavior is the behavior on integer overflow.
Но в некоторых случаях компиляторы способны детектировать UB и выдавать сообщения об этом. А в некоторых случаях должны это делать.
5.1.1.3/1
A conforming implementation shall produce at least one diagnostic message (identified in an imple-mentation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined orimplementation-defined.
И примечание к этому:
Особенно нужно обратить внимание на последнее предложение. Собственно это я и имел виду по некорректностью примера с точки зрения языка.5.1.1.3/1(9)
The intent is that an implementation should identify the nature of, and where possible localize, each violation. Of course,an implementation is free to produce any number of diagnostics as long as a valid program is still correctly translated. It may also successfully translate an invalid program.
Резюмируя: Есть язык С -> есть стандарт языка -> стандарт описывает требования, накладываемые на conforming implementation -> большинство компиляторов не являются conforming implementations по умолчанию в силу причин указанных выше -> но они предоставляют набор флагов, которые "превращают" их в conforming implementations -> используем эти флаги -> видим ошибки в коде (иногда не все, т.к. диагностика UB не всегда требуется от компилятора).
Но можно более продуктивно, без всяких компиляторов и флагов: берем стандарт языка -> видим ошибки в коде:
1) 'void main' стандарт не разрешает (номер пункта стандарта я приводил).
2) при несоответствии количества аргументов в printf колучеству спецификаторов возникает UB (номер пункта стандарта я приводил). В силу определения - UB может приводить к якобы корректному наблюдаемому поведению. Но факт наличия UB это не отменяет (да даже тот же gcc умеет выбрасывать неиспользуемую переменную, поэтому наблюдаемое поведение программы изменяется в зависимости от флагов).
К пункту 1 можно добавить примечание: конкретная conforming implementation может добавить разрешить 'void main' как часть implementation defined поведения (упомянутый GCC этого не делает).
При выкручивании диагностических сообщений на максимальный уровень соответствия стандарту обе этих ошибки(UB) прекрасно диагностируются большинством компиляторов(в т.ч. и gcc).
--std=iso9899:2017 --pedantic --pedantic-errors -Werror=format
Код: Выделить всё
<source>: In function 'print':
<source>:5:28: error: format '%d' expects a matching 'int' argument [-Werror=format=]
5 | printf("print: a=%d, b=%d\n", a), b;
| ~^
| int
<source>: At top level:
<source>:8:6: error: return type of 'main' is not 'int' [-Wmain]
8 | void main(void) {
| ^~~~
Какой там код получился - "валидный"/”законный” или нет, в результате компиляции invalid program c помощью non conforming implementation - рассуждать абсолютно бессмысленно.
PS: Замечу, что все мои выводы полностью аргументированы и подкреплены ссылками на стандарт и цитатами из него же.