6.3.0 The Governing Rule
An expression retains its type unless an explicit language operation converts it.
This is Law 2, and it is the rule from which most of this clause follows. A conversion happens when one of the following applies:
- an operator is defined to convert its operands, and this clause says how,
- a cast requests a conversion,
- an assignment, an initialization, an argument passing, or a
returnconverts a value to a known destination type, - the value is a string literal supplied to a character pointer, under clause 6.3.2.4.
Nothing else converts anything. In particular, no expression changes type because of the context that surrounds it. See syntax.md section 22.
A conversion of a value to its own type is a no-op and is always permitted.
6.3.1 Arithmetic Operands
6.3.1.1 Boolean, characters, and integers
Every integer type has an integer conversion rank, ordered as follows:
- no two signed integer types have the same rank, even when they have the same representation,
- the rank of a signed integer type is greater than the rank of any signed integer type with less width,
long longoutrankslong, which outranksint, which outranksshort, which outrankssigned char,- an unsigned integer type has the same rank as its corresponding signed integer type,
boolhas the lowest rank,charhas the same rank assigned charandunsigned char,- an enumerated type has the rank of its compatible integer type,
- the rank of an extended integer type is documented by the implementation and is relative to the standard types of the same width.
Integer promotions.
Where an int can represent all values of the original type, that value is converted to int. Otherwise it is converted to unsigned int. This applies to bool, char, signed char, unsigned char, short, unsigned short, an enumerated type whose compatible type has rank below int, and a bitfield of any of those types.
Since char is never negative and is one byte wide, char promotes to int on every data model where int is wider than one byte.
The integer promotions preserve value, including sign.
Design note. The promotion rules are inelegant, and they are load bearing. They appear in ordinary expressions, in calling conventions, in variadic argument passing, and in decades of working algorithms. The design question is not whether this rule would be invented today, but whether the irregularity does enough semantic damage to justify divergence. Here it does not. See syntax.md section 35.
ISO C99 mapping: 6.3.1.1.
6.3.1.2 Boolean type
When any scalar value is converted to bool, the result is 0 if the value compares equal to zero, and 1 otherwise.
A null pointer converts to false. Any other pointer converts to true. A value of type null_t converts to false. A floating NaN converts to true, because a NaN does not compare equal to zero. See clause 6.5.0.5.
This conversion is applied on every store into a bool object, so a bool never holds a value other than 0 or 1 regardless of what was assigned to it:
bool flag = 42; /* flag is true, holding 1 */
Note. The equality, relational, and logical operators produce int rather than bool, as they do in ISO C99. Changing them would alter sizeof(a == b), would change what a variadic argument carries, and would gain nothing that bool conversion does not already provide. See clause 6.5.0.5.
ISO C99 mapping: 6.3.1.2.
6.3.1.3 Signed and unsigned integers
Widening.
When a value is converted to an integer type of greater width, the mathematical value is preserved. A signed source value is sign-extended. An unsigned source value is zero-extended.
Narrowing.
When a value is converted to an integer type of lesser width, the low-order bits of the source representation that fit in the destination width are retained, and the resulting bit pattern is interpreted according to the destination type.
Narrowing therefore always produces a value. It never traps, and it is never implementation-defined.
For a destination of width N, the result is the unique value of the destination type congruent to the source value modulo 2^N.
int big = 300;
unsigned char b = (unsigned char)big; /* 44 */
signed char s = (signed char)big; /* 44 */
int neg = -1;
unsigned char u = (unsigned char)neg; /* 255 */
Same width, different signedness.
The bit pattern is retained and reinterpreted according to the destination type.
Difference: ISO C99 makes conversion to a signed type whose range does not contain the value implementation-defined, and permits a signal. Ocean Edition I defines it as truncation of the two's-complement representation, which is what every supported target already does. See behavior.md sections 55 and 56.
ISO C99 mapping: 6.3.1.3.
6.3.1.4 Real floating and integer
Floating to integer.
The fractional part is discarded, truncating toward zero.
If the truncated value cannot be represented in the destination integer type, the conversion causes a defined trap. Converting a NaN or an infinity to an integer type causes a defined trap.
Difference: ISO C99 makes an out-of-range floating-to-integer conversion undefined. Ocean Edition I traps, because there is no value to produce and silence would be a lie. Programs that want a clamped result should clamp before converting. See annex-d-defined-traps-and-unsafe-operations.md clause D.2.7.
Integer to floating.
If the value can be represented exactly, it is. Otherwise the result is the nearest representable value, chosen according to the current rounding mode. If the value is outside the range of the destination floating type, the conversion causes a defined trap.
ISO C99 mapping: 6.3.1.4.
6.3.1.5 Real floating types
Converting to a wider real floating type preserves the value exactly.
Converting to a narrower real floating type produces the nearest representable value, chosen according to the current rounding mode. If the magnitude exceeds the destination range, the result is an infinity of the correct sign under IEC 60559 conformance, and is otherwise implementation-defined.
An implementation shall not evaluate a floating expression in a wider format and then present the wider result where the narrower type was required, except as permitted by the evaluation method macro of clause 7.2.5.
ISO C99 mapping: 6.3.1.5.
6.3.1.6 Usual arithmetic conversions
Many binary operators convert their operands to a common type. That common type is found as follows.
- If either operand has a real floating type, the operand of lesser conversion rank among
long double,double, andfloatis converted to the type of the other, and that is the common type. - Otherwise the integer promotions are applied to both operands.
- If both promoted operands have the same type, that is the common type.
- If both have signed types, or both have unsigned types, the operand of lesser rank is converted to the type of greater rank.
- If the unsigned operand has rank greater than or equal to the rank of the signed operand, the signed operand is converted to the unsigned type.
- Otherwise, if the signed type can represent all values of the unsigned type, the unsigned operand is converted to the signed type.
- Otherwise both are converted to the unsigned type corresponding to the signed operand's type.
This is the ISO C99 rule, retained deliberately.
Diagnostics. An implementation shall diagnose an arithmetic operation in which a signed operand is converted to an unsigned type by step 5 or step 7 and the signed operand is not known to be nonnegative. The construct is valid, and it is the single most common source of silent arithmetic surprise in C, so it is a required warning rather than a silent conversion. See annex-i-diagnostics.md clause I.2.9.
Note. Comparison operators do not use this clause. See clause 6.3.1.7.
ISO C99 mapping: 6.3.1.8.
6.3.1.7 Comparison conversions
Relational and equality operators applied to two arithmetic operands compare mathematical values, not converted representations.
The rule is:
- if both operands have integer types, the comparison is performed on their mathematical values, without converting a negative signed value into a large unsigned value,
- if either operand has a real floating type, the usual arithmetic conversions of clause 6.3.1.6 are applied and the comparison is performed on the converted values,
- if the mathematical values cannot both be represented in any integer type the implementation supports, the comparison is still performed on mathematical values; an implementation shall widen, split, or otherwise arrange the comparison so that the mathematically correct answer is produced.
Therefore:
-1 < 1u /* true */
-1 == 0xFFFFFFFFu /* false */
int i = -1;
unsigned u = 1;
if (i < u) /* true */
{
...
}
Design note. This is the one place where Ocean Edition I diverges from C's conversion model, and it is worth the divergence. The traditional result of -1 < 1u being false is not an arithmetic subtlety that experienced programmers navigate; it is a defect factory. The cost is a small amount of extra code on the rare comparison where the implementation cannot prove the sign, and an implementation can usually prove it. See behavior.md section 57.
Implementation note. For operands of equal width, the comparison lowers to a sign check followed by an ordinary comparison, or to a widened comparison. For operands narrower than the widest supported integer type, promotion to that type makes the comparison exact with no extra work.
ISO C99 mapping: new. ISO C99 folds comparison into 6.3.1.8.
6.3.1.8 Default argument promotions
For an argument passed in the variadic tail of a call to a function with an ellipsis, and for an argument in a call through a pointer whose parameter type is unknown to the language, these promotions apply:
- the integer promotions of clause 6.3.1.1 are applied,
- an argument of type
floatis converted todouble, - an argument of type
null_tis converted tovoid *.
These promotions are part of the variadic calling contract. They are not a general permission for hidden type change elsewhere. See clause 6.5.2.2 and behavior.md section 103.
The null_t rule is what makes a null sentinel correct. A variadic call passes a pointer-sized null rather than an int, so the following works on every target, including those where a pointer is wider than an int:
execl(&path[0], &name[0], null);
Under ISO C99 the same call with NULL was correct only where NULL happened to expand to (void *)0.
Arguments matched against a declared parameter type are converted as if by assignment, under clause 6.5.16.1, and are not promoted.
ISO C99 mapping: 6.5.2.2.
6.3.2 Other Operands
6.3.2.1 Lvalues, arrays, and function designators
Lvalue conversion.
An lvalue that does not have array type and does not have function type, appearing where a value is required, is converted to the value stored in the designated object. If the object has automatic storage duration and its lifetime has ended, the behavior is unsafe memory behavior. The result has the unqualified version of the type of the lvalue.
Arrays do not convert.
An expression of array type has array type. It is not converted to a pointer to its first element by any context.
Given:
int values[16];
the expression values denotes the array, its type is int [16], and sizeof(values) is 16 * sizeof(int).
To obtain a pointer to the first element, write the operation:
&values[0]
Therefore:
process(&values[0]);
An array lvalue is not a modifiable lvalue, so an array cannot be assigned to. An array can be initialized, can be a member of a structure that is assigned, can have its address taken, can be subscripted, and can be the operand of sizeof.
Difference: ISO C99 converts an array expression to a pointer to its first element in almost every context. This is the largest single semantic difference in the edition. See syntax.md section 23 and behavior.md section 25.
Function designators do not convert.
A function designator has function type. It is not converted to a pointer to the function by any context.
Given:
int compare(int a, int b);
the expression compare denotes the function. The pointer is obtained by writing:
&compare
Therefore:
int (*fn)(int, int) = &compare;
Both a function designator and a function pointer may be called with ordinary call syntax. The call operator accepts either. See clause 6.5.2.2.
Difference: ISO C99 converts a function designator to a function pointer in every context except sizeof and &. See syntax.md section 25 and behavior.md section 37.
ISO C99 mapping: 6.3.2.1.
6.3.2.2 void
The value of an expression of type void shall not be used in any way.
An expression of any type may be converted to void by a cast, which discards the value while retaining the evaluation and its effects:
(void)fputs("done\n", stdout);
An implementation shall not warn about an ignored return value that has been explicitly cast to void.
ISO C99 mapping: 6.3.2.2.
6.3.2.3 Pointers
Null constants.
The null constants are the keywords null and NULL, described in clause 6.4.4.6. They have type null_t and are the only null pointer constants in the language.
A value of type null_t converts to any pointer type, without a cast, in every context where a conversion is performed. The result is a null pointer of the destination type, which designates no object and no function, and which compares unequal to a pointer to any object or function.
int *p = null;
void (*fn)(void) = null;
struct Node *head = NULL;
null_t also converts to bool, yielding false, under clause 6.3.1.2.
null_t does not convert to an integer type. There is no conversion, with or without a cast, from null_t to any integer type. A program that wants the integer zero writes 0.
The integer constant 0 is not a null constant.
An integer constant expression with the value 0 has integer type and remains an integer. It does not convert to a pointer type, and (void *)0 is an integer-to-pointer cast under the rule below rather than a null pointer constant.
int *p = 0; /* constraint violation */
int *q = (void *)0; /* an integer-to-pointer cast; not a null constant */
int *r = null; /* correct */
Difference: ISO C99 makes any integer constant expression with value 0 a null pointer constant, so a literal zero silently acquires pointer type from its context. That is the same context-sensitive retyping this edition removes from arrays and function designators, and Law 2 applies to it equally. See clause 6.4.4.6 and behavior.md section 30.
Void pointers.
A pointer to void may be converted to or from a pointer to any object type, in either direction, without a cast in an assignment context and with a cast anywhere else. The result compares equal to the original pointer.
A pointer to void shall not be converted to a pointer to function without an explicit cast. An implementation targeting an environment where code and data addresses have the same width shall define that cast to preserve the address, and shall document it.
Qualification conversions.
A pointer to a type may be converted to a pointer to a more qualified version of that type without a cast. The reverse requires a cast, and clause 6.7.3.1 states that casting away const does not make a subsequent modification valid.
Object pointer conversions.
A pointer to one object type may be converted to a pointer to another object type by a cast. If the result is not suitably aligned for the destination type, dereferencing it behaves as clause 6.2.6.6 states. Otherwise, converting the result back to the original type yields a pointer equal to the original.
Pointers and integers.
Conversion between a pointer and an integer type is performed by a cast, and the result is implementation-defined except for the round trip guaranteed by clause 6.3.2.5.
An integer converted to a pointer type need not designate a valid object. Dereferencing it is unsafe memory behavior unless the target documents the address as valid, which is the ordinary situation in memory-mapped work:
volatile uint32_t *status = (volatile uint32_t *)0x40021000u;
ISO C99 mapping: 6.3.2.3.
6.3.2.4 String literals as character pointers
A string literal has array type, as clause 6.4.5 states. This clause defines the one implicit conversion Ocean Edition I retains.
Compatible character pointer. For the purpose of this clause, a destination type is a compatible character pointer when it is a pointer to a possibly qualified character type, and the character type matches the element type of the literal in the following sense:
- for an ordinary string literal, whose element type is
const char, the destination shall be a pointer tocharqualified with at leastconst, - for a wide string literal, whose element type is
const wchar_t, the destination shall be a pointer towchar_tqualified with at leastconst.
A pointer to signed char or to unsigned char is not a compatible character pointer, because those are distinct types from char under clause 6.2.5.2. A pointer to unqualified char is not one either, because the literal is immutable under clause 6.4.5 and the conversion shall not discard the qualifier.
Rule. When a string literal appears directly as any of the following, and the destination type is a compatible character pointer, the literal is converted to a pointer to its first element:
- the right operand of an initialization,
- the right operand of a simple assignment,
- an argument matched against a declared parameter type,
- the second or third operand of a conditional operator whose other operand is such a pointer,
- the operand of a
returnstatement in a function whose return type is such a pointer.
The conversion applies to a string literal token sequence only. It does not apply to an array object, to a compound literal of array type, to a member of array type, or to any other expression.
const char *message =
"hello "
"world"; /* converted */
printf("%d\n", value); /* converted */
const char text[] = "hello";
const char *p = text; /* invalid; write &text[0] */
const char *q = &text[0]; /* correct */
char *mutable_p = "hello"; /* invalid; discards const */
unsigned char *raw = "hello"; /* invalid; not a compatible character pointer */
The explicit spelling remains available and is always valid:
const char *message = &"hello world"[0];
Design note. This is a deliberate, narrow exception to Law 2, and it is the only one. It exists because behavior.md section 82 shows a character pointer initialized from concatenated string literals, and because every call to a C library function that takes a string would otherwise need an explicit subscript. The exception is safe to make narrow: a string literal is a token, so an implementation can recognize the case syntactically without any general array conversion machinery, and no other expression acquires context-sensitive typing. A reader who wants zero exceptions can write &literal[0] everywhere and never rely on it.
ISO C99 mapping: new. In ISO C99 this case is covered by general array-to-pointer conversion.
6.3.2.5 Pointer and integer round trip
Where uintptr_t and intptr_t are provided, a pointer converted to uintptr_t and converted back to the original pointer type compares equal to the original pointer:
uintptr_t raw = (uintptr_t)p;
void *again = (void *)raw;
An implementation whose pointer representation carries metadata that cannot survive the round trip shall document that limitation rather than presenting an integer round trip that silently loses information. See behavior.md section 34.
ISO C99 mapping: 7.18.1.4, strengthened.
6.3.2.6 Conversions that do not exist
For clarity, and so that no reader looks for them:
- there is no implicit conversion from an array to a pointer, except clause 6.3.2.4,
- there is no implicit conversion from a function to a function pointer,
- there is no implicit conversion between a pointer to object and a pointer to function,
- there is no implicit conversion between two unrelated object pointer types,
- there is no implicit conversion between a pointer and an integer, in either direction,
- there is no conversion of any kind from
null_tto an integer type, - there is no conversion of any kind from an integer type to
null_t.
Each of these remains available with an explicit cast where it is meaningful.