Я пытаюсь написать строгий код, соответствующий стандарту ISO C89. Поскольку long long
не является стандартным и часто реализуется как расширение компилятора до C99, компилятор должен предупреждать меня об этом, когда я его использую. Однако при использовании gcc или clang предупреждение не появляется, когда int64_t
используется и расширяется до long long
(при 32-битной компиляции -m32
). Есть ли способ заставить компилятор предупредить меня?
Например:
/* test.c */
#include <stdint.h>
#include <stdio.h>
int main(void) {
printf("The size of an int64_t is %u.\n", (unsigned)sizeof(int64_t));
return 0;
}
Скомпилировано с помощью clang или gcc:
clang-17 -m32 -std=c89 -Wall -Wextra -Werror -pedantic -pedantic-errors test.c
# or
x86_64-pc-linux-gnu-gcc-13.2.1 -m32 -std=c89 -Wall -Wextra -Werror -pedantic -pedantic-errors test.c
Они не выдают никаких предупреждений или ошибок, даже если int64_t
на самом деле является определением типа для long long int
, поскольку clang-17 -m32 -std=c89 -E -dD -Wall -Wextra -Werror -pedantic -pedantic-errors test.c
дает мне:
...
#define __INT64_TYPE__ long long int
#define __INT64_FMTd__ "lld"
#define __INT64_FMTi__ "lli"
#define __INT64_C_SUFFIX__ LL
...
и в /path/to/clang/17/include/stdint.h
есть
...
#ifdef __INT64_TYPE__
# ifndef __int8_t_defined /* glibc sys/types.h also defines int64_t*/
typedef __INT64_TYPE__ int64_t;
# endif /* __int8_t_defined */
typedef __UINT64_TYPE__ uint64_t;
# undef __int_least64_t
...
Но если я заменю int64_t
на long long
или __INT64_TYPE__
в приведенном выше test.c
, компилятор пожалуется на это. Так почему же существует поведенческая разница между long long
и uint64_t
?
Кажется, что наиболее актуальным вопросом по SO является это и это, но их ответы, похоже, не объясняют, почему при использовании -m32
нет предупреждения. (В режиме -m64
uint64_t
не является проблемой, поскольку он расширяется до long
, что соответствует стандарту C89).
-- РЕДАКТИРОВАТЬ --
Ответ Эрика хорошо объяснил, почему и как компиляторы по-разному обрабатывают системные и пользовательские заголовки. Для тех, кому может быть интересно, помимо создания собственных наборов заголовков, я нашел один способ заставить компилятор еще и проверять соответствие стандарту C для системных заголовков:
clang -v <args>...
, чтобы сгенерировать команду, которую на самом деле запускает clang;-internal-isystem
на -I
;In file included from test.c:2:
/usr/lib/clang/17/include/stdint.h:10:1: error: // comments are not allowed in this language [-Werror,-Wcomment]
10 | // AIX system headers need stdint.h to be re-enterable while _STD_TYPES_T
| ^
/usr/lib/clang/17/include/stdint.h:52:3: error: #include_next is a language extension [-Werror,-Wgnu-include-next]
52 | # include_next <stdint.h>
| ^
2 errors generated.
это означает, что стандартная библиотека не совместима с C89, что является ожидаемым поведением.
@interjay Да, я знаю об этом, но C89 не мешает вам включать системный файл stdint.h компилятора или предоставлять свой собственный, что приводит к этой проблеме.
Стандартный int64_t
— это не long long
, это int64_t
. То, что реализация делает внутри себя, чтобы представить тип пользователю, не имеет значения. Однако использование -std=c89
и -pedantic
IMO должно привести к диагностике, поскольку int64_t
не является «стандартным типом» в C89, поэтому long long
будет расширением. Учитывая, что C89 существует более трети столетия, я подозреваю, что на пути кода, из-за которого вы отключаете компилятор, могут быть какие-то паутины.
Если вы предоставите свой собственный stdint.h, вы получите то же предупреждение, что и при прямом использовании long long
. Если вы используете системный файл stdint.h, то вы уже используете расширения, специфичные для компилятора, поэтому я не понимаю, чем может помочь предупреждение.
Для каждого стандартного заголовка создайте файл с тем же именем, которое начинается с #define int64_t DoNotUseint64_t
, а также аналогичные определения для других типов, которых вы хотите избежать. Тогда добавьте #include <absolute path to actual standard header>
. Тогда добавьте #undef int64_t
. Поместите эти файлы в каталог и скомпилируйте с помощью -isystem ThatDirectory
. (Обратите внимание, что вы должны сделать это для каждого стандартного заголовка, поскольку стандартные заголовки могут включать и определять другие, даже если стандарт C прямо не говорит об этом. Например, на моей платформе включение <stdio.h>
определяет int64_t
.)
Я не думаю, что GCC когда-либо намеревался строго соответствовать стандартам, если только это не было целью оправдать эффекты, которые может дать оптимизация компилятора. Если ваша цель — не компиляция, а проверка стандартного соответствия, то, возможно, вам нужен отдельный статический анализатор кода.
Что касается «их ответов, кажется, не объясняют, почему нет предупреждения при использовании -m32
»: предупреждение будет появляться в typedef
(а не там, где используется тип, определенный typedef
), но предупреждения подавляются в системных заголовочных файлах.
@AndrewHenle Возможно, для разработчика, который использует stdint.h
в качестве библиотеки, это не имеет значения, но я пытаюсь автоматически определить статус соответствия C89 для любого исходного файла C, и это включает в себя некоторую работу над интерфейсом компилятора, например, преобразование всех составных типов в базовые. типы... @user694733 user694733 и да, если с существующими компиляторами нет простого способа, я думаю, мне придется написать свой собственный. Мне просто интересно, есть ли что-то, чего мне не хватает в текущих компиляторах...
@EricPostpischil Но я не нашел ни одного #pragma
, который подавлял бы предупреждения в clang или Linux stdint.h
...
Отвечает ли это на ваш вопрос? Параметры GCC для строгого кода C90?
@Харит Нет, см. последний абзац вопроса и связанные вопросы.
@R-R автоматически определяет статус соответствия C89 для любого источника C. Удачи в этом. Согласно обоснованию C89: «... кодифицирует общее существующее определение C и способствует переносимости пользовательских программ в среде языка C». C89 - это язык с «наименьшим общим знаменателем», которому почти наверняка не будет строго соответствовать любой источник, существовавший с той эпохи. До C89 не было стандартизации C, и C89 все равно не получил широкого распространения. Стандартизация C на самом деле не происходила до C99 (и никогда в MSVC).
@AndrewHenle Что ж, попытка определить границу «наименьшего общего знаменателя» может оказаться интересной задачей. по крайней мере Lua утверждается, что он реализован на «чистом ANSI C»...
@R-R: #pragma
быть не обязательно. Подавление предупреждений в системных заголовках встроено в компилятор.
@EricPostpischil Ах, спасибо! Я этого не знал. Не могли бы вы превратить это в ответ?
«Я пытаюсь автоматически определить статус совместимости с C89 по любому исходному файлу C» — мне неизвестен ни один компилятор C, который бы предоставлял что-то отдаленно напоминающее полное решение этой проблемы. Конечно, GCC и Clang этого не делают. Их варианты -std=
в первую очередь связаны с предположениями, которые может безопасно сделать компилируемый исходный код, а не с тем, чтобы сделать неконфликтующие расширения недоступными или запретить программам использовать такие расширения.
Кодекс по-прежнему строго соответствует. Компилятору или приложению разрешено иметь заголовки типа #include <header.h>
, даже если они не являются частью стандартной библиотеки. Учитывая, что ничто в этих заголовках не противоречит строго соответствующей программе. Они могут содержать пользовательские типы. Идентификаторы int64_t
и т. д. ни с чем не конфликтуют в строго соответствующей реализации C89.
То, что header.h
(в данном случае stdint.h
) делает внутри соответствующего файла реализации, не является делом компилятора, если только компилятору не поручено также скомпилировать этот код, что не является обязательным требованием. Библиотека должна будет предоставить интерфейс на C через заголовочный файл, но оттуда реализация фактической библиотеки может быть доставлена в каком-либо формате библиотеки или объектного файла. Что, в свою очередь, даже не нужно писать на C.
Однако long long
недопустимо в C89, поскольку long
является зарезервированным ключевым словом, поэтому какой-либо сторонний заголовок не может предоставить такой идентификатор.
Компиляция с помощью gcc -std=c89 -pedantic-errors
дает:
ошибка: ISO C90 не поддерживает «длинный длинный» [-Wlong-long]
лязг:
ошибка: «long long» является расширением, когда режим C99 не включен [-Werror,-Wlong-long]
Что касается -m32
, то это не имеет значения для соответствия стандарту. Пока long
имеет размер не менее 32 бит, он соответствует. 32-битные приложения, конечно, могут поддерживать и 64-битные типы, хотя ЦП не способен обрабатывать их в одной инструкции.
Если вы по-прежнему настаиваете на блокировке компиляции int64_t
по какой-либо причине, вы можете добавить что-то вроде:
#pragma GCC poison int64_t
Спасибо за информацию. Но -m32
на самом деле здесь уместно, потому что int64_t
в этом случае расширяется до long long
, и компилятор не выдает никаких предупреждений, а это означает, что компилятор обрабатывает системный заголовок и мой собственный заголовок неодинаково.
@R-R Похоже, это не влияет на int64_t
, и было бы странно, если бы это повлияло.
@R-R, что означает, что компилятор обрабатывает системный заголовок и мой собственный заголовок неодинаково. Системные заголовки являются частью реализации. К ним будут относиться по-другому.
@AndrewHenle И это неравенство даже переопределяет -std=c89 -pedantic
в командной строке, и нет флага для его подавления? Я думал, что пользовательские инструкции имеют наивысший приоритет... В любом случае, я думаю, это скорее дизайнерский выбор языка и компиляторов...
Касательно «Код по-прежнему строго соответствует»: C 1990 4 гласит: «Программа, строго соответствующая стандарту, должна использовать только те функции языка и библиотеки, которые указаны в настоящем международном стандарте…» <stdint.h>
и int64_t
не указаны в стандарте 1990 года, поэтому OP test.c
не является строго соответствующей программой, как указано в C 1990. Re «Компилятору или приложению разрешено иметь заголовки типа #include <header.h>
, даже если они не являются частью стандартной библиотеки»: Да, соответствующая реализация может иметь это. Программы, использующие их, не являются строго соответствующими.
@EricPostpischil Насколько известно компилятору C89, это заголовок приложения. Если мы не сможем объявить заголовки приложений, мы не сможем писать значимые программы. #include <header.h>
определяется C89 6.8.2, хотя местоположение, к которому он сводится, конечно, определяется реализацией, то же самое происходит со стандартными заголовками, такими как #include <stdio.h>
. Синтаксис <header.h>
не определяется реализацией.
@Lundin: Re: «Если мы не сможем объявить заголовки приложений, мы не сможем писать значимые программы»: как я уже много раз писал в разных местах Stack Overflow, строго соответствующие программы весьма ограничены, в основном это просто текст. взаимодействия и абстрактные математические вычисления. Тем не менее именно это и означает строгое соответствие. Программа ОП не является строго соответствующей. Re «Насколько известно компилятору C89, это заголовок приложения»: то, что знает компилятор, не имеет значения. Программа использует что-то, не определенное в стандарте, поэтому она не является строго соответствующей.
@R-R Вы пытаетесь педантично применить древний, устаревший стандарт буквально из прошлого века, которого никто никогда широко и строго не придерживался.
@EricPostpischil Итак, вы говорите, что директива #include
не определена ISO C или иным образом не является функцией языка ISO 9899:1990. Хорошо. И вы, конечно, осознаете, что ваша личная интерпретация совершенно не соответствует логическому обоснованию 5.10? «Программа, строго соответствующая стандарту, — это еще один термин, обозначающий максимально переносимую программу. Цель состоит в том, чтобы дать программисту шанс создавать мощные программы на языке C, которые также являются хорошо переносимыми, не принижая при этом вполне полезные программы на языке C, которые оказались непереносимыми». таким образом, наречие строго».
@EricPostpischil «программы, строго соответствующие требованиям, весьма ограничены» не совсем совпадают с «мощными программами на C, которые также хорошо переносимы», не так ли?
Нет, директива #include
указана в C 1990. Включение системных заголовков определено в C 1990. Использование функции, которую системный заголовок может предоставлять в одной реализации C, но которая не указана в C 1990, не является строго соответствующим. Моя интерпретация точно соответствует логическому обоснованию. Программа OP не является максимально переносимой, поскольку она не будет работать в реализации C 1990, которая не предоставляет <stdint.h>
или не предоставляет int64_t
.
Высказывание о том, что «программы, строго соответствующие требованиям, весьма ограничены», не совсем согласуется с «мощными программами на C, которые к тому же легко переносимы», не так ли? Да, это соответствует. Строго соответствующие программы весьма ограничены: они не могут использовать графику. Они не могут использовать процедуры операционной системы. Они не могут зависеть от определенных диапазонов целочисленных типов или типов с плавающей запятой. Они не могут зависеть от набора символов. Они могут быть мощными по ожиданиям 1990 года, но они весьма ограничены.
В любом случае текст стандарта ясен. «Программа, строго соответствующая стандарту, должна использовать только те функции языка и библиотеки, которые указаны в настоящем международном стандарте…» Программа OP использует функцию, не указанную в стандарте.
@EricPostpischil И при этом нигде не сказано, что <header.h>
должно быть предусмотрено как часть реализации. Примером из реального мира может быть <pthread.h>
, который также может быть предоставлен в виде отдельно установленной сторонней библиотеки, как это делается, например, в некоторых компиляторах Windows.
Хотя мне нравится идея, что stdint.h
можно считать заголовком приложения, в конце концов, я не думаю, что это работает. Если программа полагается на систему для предоставления этого заголовка, то это не заголовок приложения. Я думаю, что тщательная интерпретация спецификации должна обнаружить, что то же самое относится и к сторонним библиотекам, не включенным в исходную форму в рассматриваемую программу. Единственная работоспособная разделительная линия проходит между тем, предоставляется ли программа сама по себе, или же программа полагается на реализацию C и среду хоста (если таковая имеется) для ее предоставления.
@Lundin: Re: «И тем не менее, нигде не сказано, что <header.h>
должно быть предоставлено как часть реализации»: вы имеете в виду, что здесь не сказано, что должны быть предоставлены заголовки стандартной библиотеки, или что не сказано, что какие-либо другие заголовки должны быть предоставлены необходимо указать через форму <…>
? Что касается первого, то стандарт, безусловно, требует, чтобы реализация предоставляла заголовки, указанные в пункте 7. Что касается второго, какова ваша точка зрения? Реализация может предоставлять или не предоставлять другие заголовки. Их использование делает программу не совсем соответствующей.
Кажется, что наиболее актуальным вопросом по SO является это и это, но их ответы, похоже, не объясняют, почему при использовании
-m32
нет предупреждения.
GCC выдаст предупреждение о typedef
(а не о том, где используется определенный тип), но GCC имеет встроенное поведение для подавления предупреждений в системных заголовках. Кланг тоже.
Потенциальным обходным решением является создание собственных версий системных заголовков, которые подавляют нежелательные определения типов. Для каждого стандартного заголовка создайте файл с таким шаблоном:
#define int64_t DoNotUseint64_t
#define uint64_t DoNotUseuint64_t
#include <absolute path to actual system header>
#undef uint64_t
#undef int64_t
Поместите эти файлы в каталог и скомпилируйте с помощью -isystem ThatDirectory
.
(Обратите внимание, что вы должны сделать это для каждого стандартного заголовка, поскольку стандартные заголовки могут включать и определять другие, даже если стандарт C прямо не говорит об этом. Например, на моей платформе включение <stdio.h>
определяет int64_t.)
Это приведет к тому, что typedef
не будет создано для int64_t
(вместо этого он будет создан для DoNotUseint64_t
), поэтому ваша программа получит ошибку компилятора, если попытается использовать int64_t
. Есть некоторая вероятность, что эти переопределения что-то сломают в стандартных заголовках, но вам придется попробовать и посмотреть.
Как отмечает Лундин, вы можете использовать #pragma GCC poison int64_t
, чтобы отметить использование int64_t
и других типов. Некоторые соображения по сравнению с методом #define
выше:
#pragma
после включения всех стандартных заголовков. (Проблема возникает, если эти включения находятся где-то кроме начала исходного файла, поскольку вам нужно отравить идентификаторы пользовательского исходного кода, а не стандартных заголовков.)int64_t
и другие подобные идентификаторы при условии, что она сама их определяет, поскольку в C 1990 это были обычные идентификаторы. Если для вас это проблема, то метод #define
, приведенный выше, позволяет этому работать, тогда как метод poison
не.
В C89 нет stdint.h.