#include <stdio.h>
int main(void) {
int x = 5;
int y = &x;
printf("Signed Value of Y: %d \n", y);
printf("Unsigned Value of Y: %u", y);
return 0;
}
Поскольку y имеет тип int, использование %d
дает вывод, возможно, со знаком, тогда как %u
дает вывод без знака. Но y имеет тип int, так почему же %u
выдает беззнаковый вывод? Существует ли неявное преобразование типов?
нет, использование неправильного спецификатора формата просто вызывает неопределенное поведение. printf
ничего не знает о типе переданного значения и должен доверять тому, что говорит спецификатор формата
Спецификаторы формата вообще не выполняют преобразования. Они говорят printf
как интерпретировать значение внутри.
Да, но это связано с тем, как вариативные функции обрабатывают параметры, а не спецификаторы формата. Например, если вы передадите char
, оно будет преобразовано в int
, float
в double
и т. д.
@EugeneSh.: Re «Спецификаторы формата вообще не выполняют преобразования»: Да, насчет этого, они на самом деле называются спецификациями преобразования. Потому что они определяют, как «аргументы преобразуются для вывода» (C 2018 7.21.6.1 2).
параметры подвергаются продвижению аргументов по умолчанию. Читать port70.net/~nsz/c/c11/n1570.html#6.5.2.2p6
@EricPostpischil Это не «преобразования типов», о которых спрашивает ОП. Должно быть ясно из контекста.
@EugeneSh.: Re «Должно быть ясно из контекста»: вы не можете ожидать, что люди, которые задают вопросы, потому что они ничего не знают, будут знать что-то из контекста.
Функция printf
понятия не имеет, какой тип переменной вы ей передали. Он полагает, что вы передали обещанный тип (используя этот спецификатор), и какие бы данные вы ни передали, printf
попытается вывести их, как если бы найденный шаблон битов был типом, который вы обещали.
Существует ли неявное преобразование типов?
Вроде, как бы, что-то вроде. Такая функция, как printf
, которая принимает переменное количество аргументов, не знает автоматически количество переменных аргументов, которые она фактически получает при любом вызове, или их типы. Спецификации преобразования, такие как %d
и %u
, в совокупности сообщают printf()
сколько переменных аргументов следует ожидать, а по отдельности они сообщают printf
какой тип ожидать для каждого аргумента. printf()
попытается интерпретировать список аргументов в соответствии с этими спецификациями.
Спецификация языка C явно отказывается говорить, что происходит, когда типы аргументов printf
не соответствуют должным образом спецификациям преобразования в прилагаемой строке формата. На практике, однако, некоторые пары типов данных имеют представления, достаточно похожие друг на друга, так что попытка printf()
интерпретировать данные одного типа, как если бы они были другого типа, с большой вероятностью (но не обязательно) создаст видимость неявного преобразования. от одного вида к другому. Такими парами обычно являются соответствующие целочисленные типы со знаком и без знака.
Вы не должны полагаться на то, что такие кажущиеся преобразования действительно происходят. Вместо этого правильно сопоставляйте типы аргументов со спецификациями преобразования. Исправьте несоответствия, выбрав другую спецификацию преобразования или выполнив соответствующее явное преобразование типа (приведение типа) к аргументу.
По сути, вызов printf
— это две разные вещи:
В любом вызове функции аргументы подготавливаются в соответствии с правилами, включающими типы аргументов и объявление функции. Они не зависят от значений аргументов, включая содержимое любой строки, переданной в качестве аргумента, и это верно и для printf
.
В вызове функции правила в основном (опуская некоторые детали):
...
объявления функции или вызываемая функция объявлена без указания типов параметров), применяются некоторые акции по умолчанию. Для целых чисел это целочисленные продвижения, которые в значительной степени (опуская некоторые детали) преобразуют типы более узкие, чем int
, в int
. Для чисел с плавающей запятой float
превращается в double
.printf
объявлен как int printf(const char * restrict format, ...);
, поэтому все его аргументы, кроме строки формата, соответствуют ...
.
Внутри printf
функция проверяет свою строку формата и пытается выполнить директивы, указанные в строке формата. Например, если директива %g
, printf
ожидает аргумент double
и берет биты из того места, где ожидается передача аргумента double
. Затем он интерпретирует эти биты как double
, создает строку в соответствии с директивой и записывает строку в стандартный вывод.
Для директивы %d
или %u
printf
ожидает аргумент int
или unsigned int
соответственно. В любом случае он берет биты из того места, где ожидает передачи аргумента int
или unsigned int
. Во всех известных мне реализациях C аргументы int
и unsigned int
передаются в одном и том же месте. Итак, если вы передадите аргумент int
, но используете %u
, printf
получит биты int
, но будет обрабатывать их так, как если бы они были битами unsigned int
. Фактическое преобразование не было выполнено; printf
просто по-разному интерпретирует биты.
Стандарт C не определяет поведение, когда вы делаете это, и реализация C будет соответствовать стандарту, если произойдет сбой, когда вы это сделаете, или если она обработает биты по-другому. Вам следует избегать этого.
«Re: Но y имеет тип int, так почему же %u выдает беззнаковый вывод?»
Из С11:
Если спецификация преобразования недействительна, поведение неопределенный. Если какой-либо аргумент не является правильным типом для соответствующей спецификации преобразования, поведение не определено.
где,
undefined — Поведение для чего-то неправильного, на котором Стандарт не предъявляет никаких требований. Всё позволено случиться, из ничего, к предупреждающему сообщению о завершении программы, к расплавление процессора, запуск ядерных ракет (при условии, что у вас есть установлена правильная аппаратная опция).
— Экспертное программирование на C.
Просто сказал: нет. Вы должны убедиться, что тип совпадает.