Неявные преобразования
Когда выражение используется в контексте, где ожидается значение другого типа, может произойти преобразование:
int n = 1L; // expression 1L has type long, int is expected n = 2.1; // expression 2.1 has type double, int is expected char* p = malloc(10); // expression malloc(10) has type void*, char* is expected
Преобразования происходят в следующих ситуациях:
Преобразование как при присваивании
- В операторе присваивания значение правого операнда преобразуется к типу левого операнда без квалификаторов.
- При скалярной инициализации значение выражения-инициализатора преобразуется к типу инициализируемого объекта без квалификаторов.
- В выражении вызова функции для функции с прототипом значение каждого выражения-аргумента преобразуется к типу соответствующего параметра без квалификаторов.
- В операторе возврата значение операнда return преобразуется к объекту с типом возвращаемого значения функции.
Обратите внимание, что фактическое присваивание, помимо преобразования, также удаляет из типов с плавающей точкой избыточный диапазон и точность и запрещает перекрытия; эти характеристики не относятся к преобразованию как при присваивании.
Стандартные продвижения аргументов
В выражении вызова функции, если вызывается
Каждый аргумент целочисленного типа подвергается целочисленному продвижению, а каждый аргумент типа float неявно преобразуется к типу double.
int add_nums(int count, ...); int sum = add_nums(2, 'c', true); // add_nums is called with three ints: (2, 99, 1)
| Обратите внимание, что в этом контексте floatcomplex и floatimaginary не продвигаются до doublecomplex и doubleimaginary. | (начиная с C99) |
Обычные арифметические преобразования
Аргументы следующих арифметических операторов подвергаются неявным преобразованиям, чтобы получить общий вещественный тип , то есть тип, в котором выполняется вычисление:
-
бинарные арифметические операторы
*,/,%,+,-, -
операторы отношения
<,>,<=,>=,==,!=, -
бинарные побитовые арифметические операторы
&,^,|, -
условный оператор
?:.
| 1) Если один операнд имеет десятичный тип с плавающей точкой, другой операнд не должен иметь стандартный тип с плавающей точкой, комплексный или мнимый тип.
| (начиная с C23) |
- целочисленный тип или вещественный тип с плавающей точкой — к longdouble
| (начиная с C99) |
- целочисленный тип или вещественный тип с плавающей точкой — к double
| (начиная с C99) |
- целочисленный тип — к float (единственный возможный вещественный тип — float, он остается без изменений)
| (начиная с C99) |
- Если типы одинаковы, этот тип является общим типом.
- Иначе типы различаются:
- Если типы имеют одинаковую знаковость (оба знаковые или оба беззнаковые), операнд с типом, имеющим меньший ранг преобразования [1], неявно преобразуется[2] к другому типу.
- Иначе операнды имеют разную знаковость:
- Если ранг преобразования беззнакового типа больше или равен рангу знакового типа, операнд со знаковым типом неявно преобразуется к беззнаковому типу.
- Иначе ранг преобразования беззнакового типа меньше ранга знакового:
- Если знаковый тип может представлять все значения беззнакового типа, операнд с беззнаковым типом неявно преобразуется к знаковому типу.
- Иначе оба операнда неявно преобразуются к беззнаковому аналогу типа знакового операнда.
- Правила ранжирования см. ниже в разделе «Целочисленные продвижения».
- См. ниже раздел «Целочисленные преобразования» в подразделе «Семантика неявных преобразований».
1.f + 20000001; // int is converted to float, giving 20000000.00
// addition and then rounding to float gives 20000000.00
(char)'a' + 1L; // first, char 'a', which is 97, is promoted to int
// different types: int and long
// same signedness: both signed
// different rank: long is of greater rank than int
// therefore, int 97 is converted to long 97
// the result is 97 + 1 = 98 of type signed long
2u - 10; // different types: unsigned int and signed int
// different signedness
// same rank
// therefore, signed int 10 is converted to unsigned int 10
// since the arithmetic operation is performed for unsigned integers
// (see “Arithmetic operators” topic), the calculation performed is (2 - 10)
// modulo (2 raised to n), where n is the number of value bits of unsigned int
// if unsigned int is 32-bit long and there is no padding bits in its object
// representation, then the result is (-8) modulo (2 raised to 32) = 4294967288
// of type unsigned int
5UL - 2ULL; // different types: unsigned long and unsigned long long
// same signedness
// different rank: rank of unsigned long long is greater
// therefore, unsigned long 5 is converted to unsigned long long 5
// since the arithmetic operation is performed for unsigned integers
// (see “Arithmetic operators” topic),
// if unsigned long long is 64-bit long, then
// the result is (5 - 2) modulo (2 raised to 64) = 3 of type
// unsigned long long
0UL - 1LL; // different types: unsigned long and signed long long
// different signedness
// different rank: rank of signed long long is greater.
// if ULONG_MAX > LLONG_MAX, then signed long long cannot represent all
// unsigned long therefore, this is the last case: both operands are converted
// to unsigned long long unsigned long 0 is converted to unsigned long long 0
// long long 1 is converted to unsigned long long 1 since the arithmetic
// operation is performed for unsigned integers
// (see “Arithmetic operators” topic),
// if unsigned long long is 64-bit long, then
// the calculation is (0 - 1) modulo (2 raised to 64)
// thus, the result is 18446744073709551615 (ULLONG_MAX) of type
// unsigned long long
| Тип результата определяется следующим образом:
double complex z = 1 + 2*I; double f = 3.0; z + f; // z remains as-is, f is converted to double, the result is double complex | (начиная с C99) |
Как и всегда, результат операции с плавающей точкой может иметь диапазон и точность больше, чем указано его типом (см. FLT_EVAL_METHOD).
| Примечание: вещественные и мнимые операнды не преобразуются неявно в комплексные, поскольку для этого потребовались бы дополнительные вычисления, приводящие к нежелательным результатам в некоторых случаях с бесконечностями, NaN и знаковыми нулями. Например, если бы вещественные значения преобразовывались в комплексные, 2.0×(3.0+i∞) вычислялось бы как (2.0+i0.0)×(3.0+i∞) ⇒ (2.0×3.0–0.0×∞) + i(2.0×∞+0.0×3.0) ⇒ NaN+i∞ вместо правильного результата 6.0+i∞. Если бы мнимые значения преобразовывались в комплексные, i2.0×(∞+i3.0) вычислялось бы как (0.0+i2.0) × (∞+i3.0) ⇒ (0.0×∞ – 2.0×3.0) + i(0.0×3.0 + 2.0×∞) ⇒ NaN + i∞ вместо –6.0 + i∞. | (начиная с C99) |
Примечание: независимо от обычных арифметических преобразований, в соответствии с правилом «как если бы» вычисление всегда может выполняться в более узком типе, чем предписывают эти правила.
Преобразования значений
Преобразование lvalue
Любое выражение lvalue любого не массивного типа, используемое в любом контексте, кроме следующих:
- в качестве операнда оператора взятия адреса (если это допускается);
- в качестве операнда префиксного или постфиксного оператора инкремента или декремента;
- в качестве левого операнда оператора доступа к члену (точка);
- в качестве левого операнда операторов присваивания и составного присваивания;
- в качестве операнда
sizeof,
подвергается преобразованию lvalue : тип остается тем же, но теряет квалификаторы const/volatile/restrict и свойства атомарности, если они имеются. Значение остается тем же, но теряет свойства lvalue (его адрес больше нельзя получить).
Если lvalue имеет неполный тип, поведение не определено.
Если lvalue обозначает объект с автоматической длительностью хранения, адрес которого никогда не брался, и этот объект не был инициализирован (не объявлен с инициализатором и до использования ему не присваивалось значение), поведение не определено.
Это преобразование моделирует загрузку значения объекта из его местоположения в памяти.
volatile int n = 1; int x = n; // lvalue conversion on n reads the value of n volatile int* p = &n; // no lvalue conversion: does not read the value of n
Преобразование массива в указатель
Любое выражение lvalue(до C99)выражение(начиная с C99) массивного типа, используемое в любом контексте, кроме следующих:
- в качестве операнда оператора взятия адреса;
- в качестве операнда
sizeof; - в качестве операнда
typeofиtypeof_unqual(начиная с C23); - в качестве строкового литерала, используемого для инициализации массива,
преобразуется в указатель, не являющийся lvalue и указывающий на его первый элемент.
Если массив был объявлен с register, поведение не определено.
Массив, не являющийся lvalue, и любой из его элементовнедоступны(до C99)имеют временную длительность жизни(начиная с C99).
int a[3], b[3][4];
int* p = a; /* conversion to &a[0] */
int (*q)[4] = b; /* conversion to &b[0] */
struct S
{
int a[1];
};
struct S f(void)
{
struct S result = {{0}}; /* {0} since C99 */
return result;
}
void g(void)
{
int* p = f().a; /* error until C99; OK since C99 */
int n = f().a[0]; /* error until C99; OK since C99 */
f().a[0] = 13; /* error until C99; UB since C99 */
(void)p, (void)n;
}
int main(void) { return 0; }
Преобразование функции в указатель
Любое выражение, обозначающее функцию, используемое в любом контексте, кроме следующих:
- в качестве операнда оператора взятия адреса;
- в качестве операнда
sizeof; - в качестве операнда
typeofиtypeof_unqual(начиная с C23),
преобразуется в указатель, не являющийся lvalue и указывающий на функцию, обозначенную выражением.
int f(int); int (*p)(int) = f; // conversion to &f (***p)(1); // repeated dereference to f and conversion back to &f
Семантика неявных преобразований
Неявное преобразование, будь то как при присваивании или обычное арифметическое преобразование , состоит из двух этапов:
Совместимые типы
Преобразование значения любого типа к любому совместимому типу всегда является пустой операцией и не меняет представление.
uint8_t (*a)[10]; // if uint8_t is a typedef to unsigned char unsigned char (*b)[] = a; // then these pointer types are compatible
Целочисленные продвижения
Целочисленное продвижение — это неявное преобразование значения любого целочисленного типа с рангом, меньшим или равным рангу типа int, либо битового поля типа _Bool(до C23)bool(начиная с C23), int, signedint, unsignedint, к значению типа int или unsignedint.
Если int может представить весь диапазон значений исходного типа (или диапазон значений исходного битового поля), значение преобразуется к типу int. В противном случае значение преобразуется к unsignedint.
| Значение битового поля целочисленного типа с точно заданной разрядностью преобразуется к соответствующему целочисленному типу с точно заданной разрядностью. В остальных случаях целочисленные типы с точно заданной разрядностью не подпадают под правила целочисленного продвижения. | (начиная с C23) |
Целочисленные продвижения сохраняют значение, включая знак:
int main(void)
{
void f(); // old-style function declaration
// since C23, void f(...) has the same behavior wrt promotions
char x = 'a'; // integer conversion from int to char
f(x); // integer promotion from char back to int
}
void f(x) int x; {} // the function expects int
Указанный выше ранг является свойством каждого целочисленного типа и определяется следующим образом:
_Bool(до C23)bool(начиная с C23) ниже ранга любого другого стандартного целочисленного типа| 8) ранг знакового целочисленного типа с точно заданной разрядностью должен быть выше ранга любого стандартного целочисленного типа с меньшей шириной или любого целочисленного типа с точно заданной разрядностью с меньшей шириной. 9) ранг любого целочисленного типа с точно заданной разрядностью относительно расширенного целочисленного типа той же ширины определяется реализацией. | (начиная с C23) |
Примечание: целочисленные продвижения применяются только
- как часть обычных арифметических преобразований (см. выше);
- как часть стандартных продвижений аргументов (см. выше);
- к операнду унарных арифметических операторов
+и-; - к операнду унарного побитового оператора
~; - к обоим операндам операторов сдвига
<<и>>.
Логическое преобразованиеЗначение любого скалярного типа может быть неявно преобразовано к _Bool(до C23)bool(начиная с C23). Значения, которые сравниваются на равенство с целочисленным константным выражением, значение которого равно нулю(до C23)являются нулем (для арифметических типов), нулевым указателем (для типов указателей) или имеют тип bool b1 = 0.5; // b1 == 1 (0.5 converted to int would be zero) bool b2 = 2.0*_Imaginary_I; // b2 == 1 (but converted to int would be zero) bool b3 = 0.0 + 3.0*I; // b3 == 1 (but converted to int would be zero) bool b4 = 0.0 / 0.0; // b4 == 1 (NaN does not compare equal to zero) bool b5 = nullptr; // b5 == 0 (since C23: nullptr is converted to false) | (начиная с C99) |
Целочисленные преобразования
Значение любого целочисленного типа может быть неявно преобразовано к любому другому целочисленному типу. За исключением случаев, рассмотренных выше в разделах о продвижениях и логических преобразованиях, действуют следующие правила:
- если целевой тип может представить это значение, значение остается неизменным;
- иначе, если целевой тип беззнаковый, значение 2b, где b — число битов значения в целевом типе, многократно вычитается из исходного значения или прибавляется к нему, пока результат не попадет в диапазон целевого типа. Другими словами, беззнаковые целые числа используют арифметику по модулю.
- иначе, если целевой тип знаковый, поведение определяется реализацией (в том числе может быть поднят сигнал).
char x = 'a'; // int -> char, result unchanged
unsigned char n = -123456; // target is unsigned, result is 192 (that is, -123456+483*256)
signed char m = 123456; // target is signed, result is implementation-defined
assert(sizeof(int) > -1); // assert fails:
// operator > requests conversion of -1 to size_t,
// target is unsigned, result is SIZE_MAX
Преобразования вещественных значений с плавающей точкой в целые
Конечное значение любого вещественного типа с плавающей точкой может быть неявно преобразовано к любому целочисленному типу. За исключением случаев, рассмотренных выше в разделе о логическом преобразовании, действуют следующие правила:
- Дробная часть отбрасывается (округление выполняется к нулю).
- Если полученное значение может быть представлено целевым типом, используется это значение.
- иначе поведение не определено.
int n = 3.14; // n == 3 int x = 1e10; // undefined behavior for 32-bit int
Значение любого целочисленного типа может быть неявно преобразовано к любому вещественному типу с плавающей точкой.
- если целевой тип может точно представить значение, оно остается неизменным.
- если значение представимо, но не может быть представлено точно, результатом становится определяемый реализацией выбор ближайшего большего или ближайшего меньшего значения, хотя при поддержке арифметики IEEE округление выполняется до ближайшего значения. В этом случае не указано, будет ли поднято исключение
FE_INEXACT. - если значение непредставимо, поведение не определено, хотя при поддержке арифметики IEEE поднимается исключение
FE_INVALID, а значение результата не определено.
Результат этого преобразования может иметь диапазон и точность больше, чем указано целевым типом (см. FLT_EVAL_METHOD).
Если при преобразованиях вещественных значений в целые требуется управление FE_INEXACT, можно использовать rint и nearbyint.
double d = 10; // d = 10.00 float f = 20000001; // f = 20000000.00 (FE_INEXACT) float x = 1 + (long long)FLT_MAX; // undefined behavior
Преобразования вещественных значений с плавающей точкой
Значение любого вещественного типа с плавающей точкой может быть неявно преобразовано к любому другому вещественному типу с плавающей точкой.
- Если целевой тип может точно представить значение, оно остается неизменным.
- если значение представимо, но не может быть представлено точно, результатом становится ближайшее большее или ближайшее меньшее значение (другими словами, направление округления определяется реализацией), хотя при поддержке арифметики IEEE округление выполняется до ближайшего значения.
- если значение непредставимо, поведение не определено.
Результат этого преобразования может иметь диапазон и точность больше, чем указано целевым типом (см. FLT_EVAL_METHOD).
double d = 0.1; // d = 0.1000000000000000055511151231257827021181583404541015625 float f = d; // f = 0.100000001490116119384765625 float x = 2 * (double)FLT_MAX; // undefined
Преобразования комплексных типовЗначение любого комплексного типа может быть неявно преобразовано к любому другому комплексному типу. Действительная и мнимая части по отдельности преобразуются по правилам для вещественных типов с плавающей точкой. double complex d = 0.1 + 0.1*I; float complex f = d; // f is (0.100000001490116119384765625, 0.100000001490116119384765625) Преобразования мнимых типовЗначение любого мнимого типа может быть неявно преобразовано к любому другому мнимому типу. Мнимая часть преобразуется по правилам для вещественных типов с плавающей точкой. double imaginary d = 0.1*_Imaginary_I; float imaginary f = d; // f is 0.100000001490116119384765625*I Преобразования вещественных и комплексных типовЗначение любого вещественного типа с плавающей точкой может быть неявно преобразовано к любому комплексному типу.
Значение любого комплексного типа может быть неявно преобразовано к любому вещественному типу с плавающей точкой.
Примечание: при преобразовании комплексного типа в вещественный NaN в мнимой части не распространяется на вещественный результат. double complex z = 0.5 + 3*I; float f = z; // the imaginary part is discarded, f is set to 0.5 z = f; // sets z to 0.5 + 0*I Преобразования вещественных и мнимых типовЗначение любого мнимого типа может быть неявно преобразовано к любому вещественному типу (целочисленному или с плавающей точкой). Результат всегда равен положительному (или беззнаковому) нулю, кроме случая, когда целевой тип — Значение любого вещественного типа может быть неявно преобразовано к любому мнимому типу. Результат всегда равен положительному мнимому нулю. double imaginary z = 3*I; bool b = z; // Boolean conversion: sets b to true float f = z; // Real-imaginary conversion: sets f to 0.0 z = 3.14; // Imaginary-real conversion: sets z to 0*_Imaginary_I Преобразования комплексных и мнимых типовЗначение любого мнимого типа может быть неявно преобразовано к любому комплексному типу.
Значение любого комплексного типа может быть неявно преобразовано к любому мнимому типу.
double imaginary z = I * (3*I); // the complex result -3.0+0i loses real part
// sets z to 0*_Imaginary_I | (начиная с C99) |
Преобразования указателей
Указатель на void может быть неявно преобразован к указателю на объект любого типа и обратно со следующей семантикой:
- Если указатель на объект преобразуется к указателю на void, а затем обратно, его значение сравнивается как равное исходному указателю.
- Других гарантий не предоставляется.
int* p = malloc(10 * sizeof(int)); // malloc returns void*
Указатель на тип без квалификаторов может быть неявно преобразован к указателю на квалифицированную версию этого типа (другими словами, можно добавить квалификаторы const, volatile и restrict). Исходный указатель и результат сравниваются как равные.
int n; const int* p = &n; // &n has type int*
Любое целочисленное константное выражение со значением 0, а также целочисленное выражение-указатель со значением ноль, приведенное к типу void*, могут быть неявно преобразованы к любому типу указателя (как к указателю на объект, так и к указателю на функцию). Результатом является значение нулевого указателя соответствующего типа, которое гарантированно сравнивается как неравное любому ненулевому значению указателя этого типа. Такое целочисленное выражение или выражение void* называется константой нулевого указателя; стандартная библиотека предоставляет определение этой константы в виде макроса NULL.
int* p = 0; double* q = NULL;
Примечания
Хотя переполнение знакового целого числа в любом арифметическом операторе является неопределенным поведением, переполнение знакового целочисленного типа при целочисленном преобразовании является лишь поведением, не заданным стандартом.
С другой стороны, хотя переполнение беззнакового целого числа в любом арифметическом операторе (и при целочисленном преобразовании) является четко определенной операцией и следует правилам арифметики по модулю, переполнение беззнакового целого числа при преобразовании вещественного значения в целое является неопределенным поведением: значения вещественного типа с плавающей точкой, которые можно преобразовать в беззнаковое целое число, принадлежат открытому интервалу (-1, Unnn_MAX + 1).
unsigned int n = -1.0; // undefined behavior
Преобразования между указателями и целыми числами (за исключением преобразования указателя в _Bool(до C23)bool(с C23) и(с C99)преобразования целочисленного константного выражения со значением ноль в указатель), между указателями на объекты (за исключением случаев, когда исходный или целевой указатель является указателем на void), а также преобразования между указателями на функции (за исключением случаев, когда функции имеют совместимые типы) никогда не выполняются неявно и требуют оператора приведения типа.
Преобразования (неявные или явные) между указателями на функции и указателями на объекты (включая void*) или целыми числами отсутствуют.
Ссылки
- Стандарт C23 (ISO/IEC 9899:2024):
- 6.3 Преобразования (с. 44–50)
- Стандарт C17 (ISO/IEC 9899:2018):
- 6.3 Преобразования (с. 37–41)
- Стандарт C11 (ISO/IEC 9899:2011):
- 6.3 Преобразования (с. 50–56)
- Стандарт C99 (ISO/IEC 9899:1999):
- 6.3 Преобразования (с. 42–48)
- Стандарт C89/C90 (ISO/IEC 9899:1990):
- 3.2 Преобразования
См. также
| Документация по C++ о неявных преобразованиях |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/c/language/conversion