Компилятор не выдает предупреждение о uint64_t в строгом режиме C89

Я пытаюсь написать строгий код, соответствующий стандарту 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 нет предупреждения. (В режиме -m64uint64_t не является проблемой, поскольку он расширяется до long, что соответствует стандарту C89).

-- РЕДАКТИРОВАТЬ --

Ответ Эрика хорошо объяснил, почему и как компиляторы по-разному обрабатывают системные и пользовательские заголовки. Для тех, кому может быть интересно, помимо создания собственных наборов заголовков, я нашел один способ заставить компилятор еще и проверять соответствие стандарту C для системных заголовков:

  1. Используйте clang -v <args>..., чтобы сгенерировать команду, которую на самом деле запускает clang;
  2. Замените все -internal-isystem на -I;
  3. Запустите измененную команду, затем clang выведет:
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, что является ожидаемым поведением.

В C89 нет stdint.h.

interjay 30.04.2024 14:22

@interjay Да, я знаю об этом, но C89 не мешает вам включать системный файл stdint.h компилятора или предоставлять свой собственный, что приводит к этой проблеме.

R-R 30.04.2024 14:27

Стандартный int64_t — это не long long, это int64_t. То, что реализация делает внутри себя, чтобы представить тип пользователю, не имеет значения. Однако использование -std=c89 и -pedantic IMO должно привести к диагностике, поскольку int64_t не является «стандартным типом» в C89, поэтому long long будет расширением. Учитывая, что C89 существует более трети столетия, я подозреваю, что на пути кода, из-за которого вы отключаете компилятор, могут быть какие-то паутины.

Andrew Henle 30.04.2024 14:30

Если вы предоставите свой собственный stdint.h, вы получите то же предупреждение, что и при прямом использовании long long. Если вы используете системный файл stdint.h, то вы уже используете расширения, специфичные для компилятора, поэтому я не понимаю, чем может помочь предупреждение.

interjay 30.04.2024 14:35

Для каждого стандартного заголовка создайте файл с тем же именем, которое начинается с #define int64_t DoNotUseint64_t, а также аналогичные определения для других типов, которых вы хотите избежать. Тогда добавьте #include <absolute path to actual standard header>. Тогда добавьте #undef int64_t. Поместите эти файлы в каталог и скомпилируйте с помощью -isystem ThatDirectory. (Обратите внимание, что вы должны сделать это для каждого стандартного заголовка, поскольку стандартные заголовки могут включать и определять другие, даже если стандарт C прямо не говорит об этом. Например, на моей платформе включение <stdio.h> определяет int64_t.)

Eric Postpischil 30.04.2024 14:40

Я не думаю, что GCC когда-либо намеревался строго соответствовать стандартам, если только это не было целью оправдать эффекты, которые может дать оптимизация компилятора. Если ваша цель — не компиляция, а проверка стандартного соответствия, то, возможно, вам нужен отдельный статический анализатор кода.

user694733 30.04.2024 14:41

Что касается «их ответов, кажется, не объясняют, почему нет предупреждения при использовании -m32»: предупреждение будет появляться в typedef (а не там, где используется тип, определенный typedef), но предупреждения подавляются в системных заголовочных файлах.

Eric Postpischil 30.04.2024 14:50

@AndrewHenle Возможно, для разработчика, который использует stdint.h в качестве библиотеки, это не имеет значения, но я пытаюсь автоматически определить статус соответствия C89 для любого исходного файла C, и это включает в себя некоторую работу над интерфейсом компилятора, например, преобразование всех составных типов в базовые. типы... @user694733 user694733 и да, если с существующими компиляторами нет простого способа, я думаю, мне придется написать свой собственный. Мне просто интересно, есть ли что-то, чего мне не хватает в текущих компиляторах...

R-R 30.04.2024 14:51

@EricPostpischil Но я не нашел ни одного #pragma, который подавлял бы предупреждения в clang или Linux stdint.h...

R-R 30.04.2024 14:56

Отвечает ли это на ваш вопрос? Параметры GCC для строгого кода C90?

Harith 30.04.2024 15:04

@Харит Нет, см. последний абзац вопроса и связанные вопросы.

R-R 30.04.2024 15:06

@R-R автоматически определяет статус соответствия C89 для любого источника C. Удачи в этом. Согласно обоснованию C89: «... кодифицирует общее существующее определение C и способствует переносимости пользовательских программ в среде языка C». C89 - это язык с «наименьшим общим знаменателем», которому почти наверняка не будет строго соответствовать любой источник, существовавший с той эпохи. До C89 не было стандартизации C, и C89 все равно не получил широкого распространения. Стандартизация C на самом деле не происходила до C99 (и никогда в MSVC).

Andrew Henle 30.04.2024 15:22

@AndrewHenle Что ж, попытка определить границу «наименьшего общего знаменателя» может оказаться интересной задачей. по крайней мере Lua утверждается, что он реализован на «чистом ANSI C»...

R-R 30.04.2024 15:33

@R-R: #pragma быть не обязательно. Подавление предупреждений в системных заголовках встроено в компилятор.

Eric Postpischil 30.04.2024 15:48

@EricPostpischil Ах, спасибо! Я этого не знал. Не могли бы вы превратить это в ответ?

R-R 30.04.2024 15:55

«Я пытаюсь автоматически определить статус совместимости с C89 по любому исходному файлу C» — мне неизвестен ни один компилятор C, который бы предоставлял что-то отдаленно напоминающее полное решение этой проблемы. Конечно, GCC и Clang этого не делают. Их варианты -std= в первую очередь связаны с предположениями, которые может безопасно сделать компилируемый исходный код, а не с тем, чтобы сделать неконфликтующие расширения недоступными или запретить программам использовать такие расширения.

John Bollinger 30.04.2024 16:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
16
115
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Кодекс по-прежнему строго соответствует. Компилятору или приложению разрешено иметь заголовки типа #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 30.04.2024 15:35

@R-R Похоже, это не влияет на int64_t, и было бы странно, если бы это повлияло.

Lundin 30.04.2024 15:37

@R-R, что означает, что компилятор обрабатывает системный заголовок и мой собственный заголовок неодинаково. Системные заголовки являются частью реализации. К ним будут относиться по-другому.

Andrew Henle 30.04.2024 15:40

@AndrewHenle И это неравенство даже переопределяет -std=c89 -pedantic в командной строке, и нет флага для его подавления? Я думал, что пользовательские инструкции имеют наивысший приоритет... В любом случае, я думаю, это скорее дизайнерский выбор языка и компиляторов...

R-R 30.04.2024 15:51

Касательно «Код по-прежнему строго соответствует»: C 1990 4 гласит: «Программа, строго соответствующая стандарту, должна использовать только те функции языка и библиотеки, которые указаны в настоящем международном стандарте…» <stdint.h> и int64_t не указаны в стандарте 1990 года, поэтому OP test.c не является строго соответствующей программой, как указано в C 1990. Re «Компилятору или приложению разрешено иметь заголовки типа #include <header.h>, даже если они не являются частью стандартной библиотеки»: Да, соответствующая реализация может иметь это. Программы, использующие их, не являются строго соответствующими.

Eric Postpischil 30.04.2024 15:54

@EricPostpischil Насколько известно компилятору C89, это заголовок приложения. Если мы не сможем объявить заголовки приложений, мы не сможем писать значимые программы. #include <header.h> определяется C89 6.8.2, хотя местоположение, к которому он сводится, конечно, определяется реализацией, то же самое происходит со стандартными заголовками, такими как #include <stdio.h>. Синтаксис <header.h> не определяется реализацией.

Lundin 30.04.2024 16:00

@Lundin: Re: «Если мы не сможем объявить заголовки приложений, мы не сможем писать значимые программы»: как я уже много раз писал в разных местах Stack Overflow, строго соответствующие программы весьма ограничены, в основном это просто текст. взаимодействия и абстрактные математические вычисления. Тем не менее именно это и означает строгое соответствие. Программа ОП не является строго соответствующей. Re «Насколько известно компилятору C89, это заголовок приложения»: то, что знает компилятор, не имеет значения. Программа использует что-то, не определенное в стандарте, поэтому она не является строго соответствующей.

Eric Postpischil 30.04.2024 16:05

@R-R Вы пытаетесь педантично применить древний, устаревший стандарт буквально из прошлого века, которого никто никогда широко и строго не придерживался.

Andrew Henle 30.04.2024 16:05

@EricPostpischil Итак, вы говорите, что директива #include не определена ISO C или иным образом не является функцией языка ISO 9899:1990. Хорошо. И вы, конечно, осознаете, что ваша личная интерпретация совершенно не соответствует логическому обоснованию 5.10? «Программа, строго соответствующая стандарту, — это еще один термин, обозначающий максимально переносимую программу. Цель состоит в том, чтобы дать программисту шанс создавать мощные программы на языке C, которые также являются хорошо переносимыми, не принижая при этом вполне полезные программы на языке C, которые оказались непереносимыми». таким образом, наречие строго».

Lundin 30.04.2024 16:14

@EricPostpischil «программы, строго соответствующие требованиям, весьма ограничены» не совсем совпадают с «мощными программами на C, которые также хорошо переносимы», не так ли?

Lundin 30.04.2024 16:15

Нет, директива #include указана в C 1990. Включение системных заголовков определено в C 1990. Использование функции, которую системный заголовок может предоставлять в одной реализации C, но которая не указана в C 1990, не является строго соответствующим. Моя интерпретация точно соответствует логическому обоснованию. Программа OP не является максимально переносимой, поскольку она не будет работать в реализации C 1990, которая не предоставляет <stdint.h> или не предоставляет int64_t.

Eric Postpischil 30.04.2024 16:18

Высказывание о том, что «программы, строго соответствующие требованиям, весьма ограничены», не совсем согласуется с «мощными программами на C, которые к тому же легко переносимы», не так ли? Да, это соответствует. Строго соответствующие программы весьма ограничены: они не могут использовать графику. Они не могут использовать процедуры операционной системы. Они не могут зависеть от определенных диапазонов целочисленных типов или типов с плавающей запятой. Они не могут зависеть от набора символов. Они могут быть мощными по ожиданиям 1990 года, но они весьма ограничены.

Eric Postpischil 30.04.2024 16:21

В любом случае текст стандарта ясен. «Программа, строго соответствующая стандарту, должна использовать только те функции языка и библиотеки, которые указаны в настоящем международном стандарте…» Программа OP использует функцию, не указанную в стандарте.

Eric Postpischil 30.04.2024 16:21

@EricPostpischil И при этом нигде не сказано, что <header.h> должно быть предусмотрено как часть реализации. Примером из реального мира может быть <pthread.h>, который также может быть предоставлен в виде отдельно установленной сторонней библиотеки, как это делается, например, в некоторых компиляторах Windows.

Lundin 30.04.2024 16:23

Хотя мне нравится идея, что stdint.h можно считать заголовком приложения, в конце концов, я не думаю, что это работает. Если программа полагается на систему для предоставления этого заголовка, то это не заголовок приложения. Я думаю, что тщательная интерпретация спецификации должна обнаружить, что то же самое относится и к сторонним библиотекам, не включенным в исходную форму в рассматриваемую программу. Единственная работоспособная разделительная линия проходит между тем, предоставляется ли программа сама по себе, или же программа полагается на реализацию C и среду хоста (если таковая имеется) для ее предоставления.

John Bollinger 30.04.2024 16:40

@Lundin: Re: «И тем не менее, нигде не сказано, что <header.h> должно быть предоставлено как часть реализации»: вы имеете в виду, что здесь не сказано, что должны быть предоставлены заголовки стандартной библиотеки, или что не сказано, что какие-либо другие заголовки должны быть предоставлены необходимо указать через форму <…>? Что касается первого, то стандарт, безусловно, требует, чтобы реализация предоставляла заголовки, указанные в пункте 7. Что касается второго, какова ваша точка зрения? Реализация может предоставлять или не предоставлять другие заголовки. Их использование делает программу не совсем соответствующей.

Eric Postpischil 30.04.2024 18:22
Ответ принят как подходящий

Кажется, что наиболее актуальным вопросом по 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 после включения всех стандартных заголовков. (Проблема возникает, если эти включения находятся где-то кроме начала исходного файла, поскольку вам нужно отравить идентификаторы пользовательского исходного кода, а не стандартных заголовков.)
  • Программа, строго соответствующая C 1990, может использовать int64_t и другие подобные идентификаторы при условии, что она сама их определяет, поскольку в C 1990 это были обычные идентификаторы. Если для вас это проблема, то метод #define, приведенный выше, позволяет этому работать, тогда как метод poison не.

Другие вопросы по теме