#c #gcc #arm #cortex-m
#c #gcc #arm #cortex-m
Вопрос:
Я использую arm-none-eabi-gcc
7.4 для компиляции «простой» программы для микроконтроллера на базе ARM Cortex-M4 (в частности, EFM32WG940, но это не должно иметь отношения к вопросу).
Насколько я вижу, sizeof(int)
и sizeof(long int)
наряду с их неподписанными аналогами являются обоими 4
. На самом деле вам нужно ввести long long int
, чтобы получить 64-разрядное целое число (или int64_t
), поэтому long int
это кажется совершенно излишним.
Почему в этой среде для __UINT32_TYPE__
предопределенного макроса определено значение long unsigned int
?
Есть ли способ изменить, uint32_t
чтобы быть unsigned int
вместо этого?
Обычно это меня не беспокоит, но из-за printf
этого мне приходится нелегко -Wformat
. Рассмотрим что-то вроде: printf("hello %un", i);
(предполагая, что i
есть uint32_t
), который выдает мне предупреждение, потому что %u
ожидает unsigned
, но uint32_t
есть unsigned long
. Очевидно, я могу изменить его на %lu
, но тогда тот же код выдаст мне предупреждение при компиляции на x86.
Редактировать:
Да, один из способов заставить замолчать -Wformat
— это использовать форматные макросы из inttypes.h
вот так: printf("hello %" PRIu32, i);
которые, хотя на самом деле не отвечают на мой вопрос, были бы вполне приемлемыми, если бы код не был таким уродливым, и у некоторых статических анализаторов не было проблем с этим.
Вместо того, чтобы взламывать все мои вызовы printf, я хотел бы понять, почему uint32_t
должно быть unsigned long int
вместо unsigned int
на этой платформе, и как это изменить.
Комментарии:
1. en.cppreference.com/w/c/types/integer#Format_macro_constants — Существует стандартный способ их печати, реализация также должна позаботиться об этом
2. @StoryTeller — это правильный способ сделать это. Не существует соответствующего способа изменить однажды сделанное объявление типа. Также нет соответствующего способа предоставить ваше собственное объявление идентификатора, указанного стандартом, для использования, указанного в стандарте для него. Если вы должны использовать типы с явной шириной, то к ним прилагаются макросы формата.
3. Только разработчики GCC, которые приняли решение, могут ответить на «почему» часть вопроса окончательно, @Venemo. Остальные из нас могут только строить догадки. Но обратите внимание, что некоторые реализации имеют 16-разрядные
int
s, и на нихunsigned int
не будут соответствовать критериям дляuint32_t
. Эти реализации включают GCC на некоторых архитектурах, гдеunsigned long
было бы правильно, ноunsigned int
не будет.4. Какие форматы не предоставляют «голые металлические реализации», @P__J__? Обратите внимание, что мы говорим об одном из
"%lu"
и"%u"
. Использование соответствующего макроса для выбора правильного из них не имеет значения размера кода. Конечно, автономным реализациям не нужно предоставлять соответствующую стандартную библиотеку (или любую стандартную библиотеку вообще), но маловероятно, что какая-либо реализация предоставитprintf()
, но не форматы для типов, которые она поддерживает.5. Они не определяют это каким образом, @P__J__? Мы говорим о стандартных функциях языка, и GCC / glibc действительно соответствует в этой области.
Ответ №1:
Насколько я вижу,
sizeof(int)
иsizeof(long int)
вместе с их беззнаковыми аналогами оба 4. На самом деле вам нужно ввестиlong long int
, чтобы получить 64-разрядное целое число (илиint64_t
), поэтомуlong int
это кажется совершенно излишним.
Язык C не определяет точную ширину стандартных целочисленных типов. Скорее, он определяет минимальный диапазон значений, которые каждое из них должно представлять. Это позволяет писать C естественным образом для целей с различной архитектурой. В частности, соответствующая реализация может предоставлять int
s всего 15 битов значений, тогда как long int
должно быть не менее 31, а long long int
должно быть не менее 63. Целочисленные типы со знаком также имеют знаковый бит, а беззнаковые аналоги имеют один дополнительный бит значения. (Любой из них также может содержать биты заполнения, хотя это редко.) Так что нет, long int
не является лишним, по крайней мере, не в семантическом смысле.
Почему в этой среде для
__UINT32_TYPE__
предопределенного макроса определено значениеlong unsigned int
?
Во-первых, __UINT32_TYPE__
это детали реализации glibc. Таким образом, единственный разумный ответ на этот поставленный вопрос — «потому что unsigned long int
это тип, выбранный для uint32_t
«. Но я полагаю, что вы на самом деле спрашиваете, почему этот тип был выбран в первую очередь. Я не могу однозначно объяснить причины решений разработчиков GCC / glibc здесь, но я думаю, что я бы сделал тот же выбор на их месте, где они пытаются поддерживать широкий спектр архитектур.
Поскольку long int
и unsigned long int
гарантированно имеют по крайней мере 31 и 32 бита значений соответственно (а int
и unsigned int
таковыми не являются), длинные версии являются естественным выбором для [ u
] int32_t
на тех платформах, где они фактически соответствуют его требованиям (всего ровно 32 бита, без битов заполнения, а подписанная версия реализована как дополнение two). Длинные версии являются единственно правильным выбором для некоторых поддерживаемых архитектур, и они являются одним из приемлемых вариантов для большинства остальных. Выбирая длинные версии, glibc необходимо делать исключения только для подмножества 64-разрядных платформ и, возможно, нескольких платформ с нечетными целочисленными типами.
Нет требования и нет оправдания для каких-либо ожиданий, что int
и unsigned int
будут типами, выбранными для int32_t
и uint32_t
когда они удовлетворяют требованиям для этого, если только они не являются единственными доступными типами, которые удовлетворяют требованиям.
Есть ли способ изменить,
uint32_t
чтобы бытьunsigned int
вместо этого?
Для вашего кода C нет соответствующего способа изменить определение uint32_t
, предоставляемое реализацией. Вы могли бы попробовать взломать свою реализацию C, чтобы внести изменения, или вы могли бы выбрать совершенно другую реализацию, но любая попытка переопределить уже определенный тип приводит к неопределенному поведению (и, вероятно, но не обязательно, отклоняется компилятором).
Стандартный способ справиться с изменчивостью типов при использовании типов с явной шириной с форматированными функциями ввода-вывода — это использовать макросы, предоставленные для этой цели, о которых вы уже знаете. В качестве альтернативы, если вы удовлетворены тем, что знаете применимые определения используемых типов для всех интересующих платформ, тогда вы можете напрямую записать соответствующие форматы. Или вы можете использовать стандартный тип по вашему выбору напрямую (например unsigned int
) или, для printf
но не scanf
, привести аргумент к типу, соответствующему формату.
Не стоит легкомысленно отвергать альтернативу использования стандартных типов вместо псевдонимов типа явной ширины. Первые являются более плавной альтернативой, где их достаточно — нет необходимости в каких-либо специальных заголовках и обеспечивают плавную совместимость со всеми стандартными библиотечными функциями.
Очевидно, я могу изменить его на
%lu
, но тогда тот же код выдаст мне предупреждение при компиляции на x86.
Может быть, так и будет, а может и нет. Тип unsigned long
имеет ширину 32 бита в glibc и для 32-разрядной версии x86. Я не уверен, что это тип, выбранный для int32_t
там, но это одна из жизнеспособных альтернатив. То же самое справедливо даже для некоторых 64-разрядных платформ, таких как Win64. Для любой заданной платформы, где жизнеспособны обе альтернативы, вы, вероятно, найдете некоторые реализации, которые выбирают одну, а некоторые — другую.
Комментарии:
1. Ваш ответ строго теоретический — вы указали, что у вас нет опыта в программировании uC. Все порты arm-gcc, которые я знаю, определяют псевдоним uint32_t для unsigned int. Вероятно, файлы заголовков OPs toolchain имеют 8-битный фон uC, и они определили его как
unsigned long
(для 8-битных UCS обычно int равен 16 битам). Во всех цепочках инструментов, которые я использую, нет предупреждений: godbolt.org/z/2bnTz52. @P__J__ В проводнике компилятора вы выбрали «ARM GCC», что не совпадает с
arm-none-eabi-gcc
моим вопросом. Вот тот же код в правильном варианте GCC, и вы можете увидеть точное предупреждение, о котором я говорю. godbolt.org/z/8Cc_ii3. @P__J__, мой ответ — это основанный на стандартах ответ на фактически поставленные вопросы: почему реализация делает выбор, который она делает, и могут ли они быть переопределены. Он принимает неявную предпосылку о том, что OP точно охарактеризовал ситуацию (что, по-видимому, поддается проверке), но даже если они этого не сделали, это просто делает вопрос гипотетическим.