非常教程

C参考手册

C 语法

Implicit conversions

当预期在不同类型的值的上下文中使用表达式时,可能会发生转换

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语句中,操作数的值return被转换为具有函数返回类型的对象

请注意,除转换外,实际分配也会从浮点类型中移除额外的范围和精度,并禁止重叠; 这些特性不适用于转换,就像通过分配一样。

默认参数优化

在调用时的函数调用表达式中。

1)没有原型的功能

2)可变参数函数,其中参数表达式是与省略号参数匹配的尾随参数之一

整数类型的每个参数都经过整数提升(参见下文),并且每个类型的参数都float被隐式转换为类型double

int add_nums(int count, ...);
int sum = add_nums(2, 'c', true); // add_nums is called with three ints: (2, 99, 1)

请注意,float complexfloat imaginary没有提升到double complexdouble imaginary在这种情况下。

通常的算术转换

以下算术运算符的参数经过隐式转换,以获得公共实数类型,这是执行计算的类型:

  • 二进制算术*,/,%,+, -
  • 关系运算符<,>,<=,> =,==,!=
  • 二进制按位运算&,^,|,
  • 条件操作符?:

1)如果一个操作数是long doublelong double complexlong double imaginary,另一个操作数被隐式转换如下:

  • 整数或实际浮动类型 long double
  • 复杂类型 long double complex
  • 虚构类型 long double imaginary

2)否则,如果一个操作数是doubledouble complexdouble imaginary,另一个操作数被隐式转换如下:

  • 整数或实际浮动类型 double
  • 复杂类型 double complex
  • 虚构类型 double imaginary

3)否则,如果一个操作数是floatfloat complexfloat imaginary,另一个操作数被隐式转换如下:

  • 整数类型为float(唯一可能的实际类型是float,保持原样)
  • 复杂类型依然存在 float complex
  • 虚构类型仍然存在 float imaginary

4)否则,两个操作数都是整数。在这种情况下,首先,两个操作数都进行整数升级(见下文)。然后

  • 如果促销后的类型相同,则该类型是常见类型
  • 否则,如果促销后的两个操作数具有相同的签名(均为有符号或无符号),则具有较低转换等级的操作数(请参见下文)会隐式转换为具有较高转换等级的操作数的类型
  • 否则,签名会有所不同:如果具有无符号类型的操作数的转换等级大于或等于有符号操作数类型的等级,则带符号类型的操作数将隐式转换为无符号类型
  • 否则,签名是不同的,并且带符号的操作数的等级大于无符号的操作数的等级。在这种情况下,如果有符号类型可以表示所有无符号类型的值,那么具有无符号类型的操作数将隐式转换为有符号操作数的类型。
  • 否则,两个操作数都会隐式转换为带符号操作数类型的无符号类型。
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 is promoted back to int.
                // this is signed + signed case, different rank
                // int is converted to long, the result is 98 signed long
2u - 10; // signed / unsigned, same rank
         // 10 is converted to unsigned, unsigned math is modulo UINT_MAX+1
         // assuming 32 bit ints, result is 4294967288 of type unsigned int (aka UINT_MAX-7)
0UL - 1LL; // signed/unsigned diff rank, rank of signed is greater.
           // If sizeof(long) == sizeof(long long), signed cannot represent all unsigned
           // this is the last case: both operands are converted to unsigned long long
           // 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

与往常一样,浮点运算符的结果可能比其类型所指示的范围和精度更高(请参阅参考资料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∞。

注意:无论通常的算术转换如何,在as-if规则下,通过这些规则,计算总是可以以窄于指定符的类型执行。

价值转化

左值转换

任何非数组类型的任何左值表达式,当在除。以外的任何上下文中使用时。

  • 作为操作符的操作数(如果允许)
  • 作为前/后增量和减量运算符的操作数。
  • 作为成员访问(点)运算符的左侧操作数。
  • 作为赋值和复合赋值操作符的左侧操作数。
  • 作为 sizeof 的操作数

进行左值转换:类型保持不变,但会丢失 const / volatile / restrict-qualifiers 和原子属性(如果有的话)。价值保持不变,但失去了左值属性(地址可能不再被采用)。

如果左值具有不完整类型,则行为未定义。

如果左值指定一个自动存储持续时间的对象,其地址从未被采用,并且该对象未初始化(未使用初始化器声明并且在使用之前未对其进行分配),则行为是未定义的。

此转换将模拟对象的值从其位置的内存负载。

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

数组到指针的转换

数组类型的任何左值表达式,当在除。以外的任何上下文中使用时。

  • 作为操作符地址的操作数
  • 作为 sizeof 的操作数
  • 作为用于数组初始化的字符串文字

经历转换到非左值指针到它的第一个元素。

如果数组被声明为注册,则行为是未定义的。

int a[3], b[3][4];
int* p = a;      // conversion to &a[0]
int (*q)[4] = b; // conversion to &b[0]

函数指针转换

任何函数指示符表达式,当在除。以外的任何上下文中使用时。

  • 作为操作符地址的操作数
  • 作为sizeof的操作数

经过转换到非左值指针指向由表达式指定的函数。

int f(int);
int (*p)(int) = f; // conversion to &f
(***p)(1); // repeated dereference to f and conversion back to &f

隐式转换语义

隐式转换,无论是通过赋值还是通常的算术转换,都由两个阶段组成:

1)价值转化(如果适用)

2)下面列出的转换之一(如果它可以产生目标类型)

兼容的类型

将任何类型的值转换为任何兼容类型始终是不可操作的,并且不会更改表示形式。

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,整型,符号int,unsigned int类型的位域的,以类型的值intunsigned int

如果int可以表示原始类型的整个值范围(或原始位域的值的范围),则将该值转换为类型int。否则,该值将转换为unsigned int

整数升级保持价值,包括符号:

int main(void) {
   void f(); // old-style function declaration
   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

上面的 rank 是每个整数类型的属性,定义如下:

1)所有有符号整数类型的行列是不同的,并且随着它们的精度而增加:被签名的char的等级<短的等级<int的等级<long int的等级<long long 的等级 int

2)所有有符号整数类型的等级等于对应无符号整数类型的等级

3)任何标准整数类型的等级大于任何相同大小的扩展整数类型的等级(即,__int64 <等级长long int,但 long long 等级<由于规则而等于__int128等级(1))

4)char 的等级等于 signed char 的等级和 unsigned char 的等级

5)_Bool 的排名小于任何其他标准整数类型的排名

6)任何枚举类型的等级等于其兼容整数类型的等级

7)排名是传递性的:如果T1的等级<T2的等级和T2的等级<T3的等级,那么T1的等级<T3的等级

8)上面未涉及的扩展整数类型的相对排序的任何方面都是实现定义的

注意:整数升级只适用。

  • 作为通常算术转换的一部分(见上文)
  • 作为默认参数促销的一部分(参见上文)
  • 到一元算术运算符的操作数+和 -
  • 到一元位运算符〜的操作数
  • 移位运算符<<和>>的两个操作数

布尔转换

任何标量类型的值都可以隐式转换为_Bool。将等于零的值转换为​0​,将其他所有值转换为1

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)

整数转换

任何整数类型的值都可以隐式转换为任何其他整数类型。除了上述促销和布尔转换所涵盖的地方外,规则如下:

  • 如果目标类型可以表示源类型的整个值范围,则值不变
  • 否则,如果目标类型是无符号的,则将值2 b(其中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

Real floating-integer conversions

任何实数浮点类型的有限值都可以隐式转换为任何整数类型。除了上述布尔转换覆盖的地方,规则是:

  • 小数部分被丢弃(截至零)。
    • 如果结果值可以用目标类型表示,则使用该值
    • 否则,行为是不确定的
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在浮点到整数转换中需要控制,rintnearbyint可以使用。

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

真正复杂的转换

任何实际浮动类型的值都可以隐式转换为任何复杂类型。

  • 结果的实际部分取决于实际浮动类型的转换规则
  • 结果的虚部为正零(或非IEEE系统上的无符号零)

任何复杂类型的值都可以隐式转换为任何实际浮动类型。

  • 实部按照真实浮动类型的规则进行转换
  • 虚部被丢弃

注意:在复数到实数转换中,虚数部分的 NaN 不会传播到实际结果。

实际的虚拟转换

任何虚数类型的值都可以隐式转换为任何实数类型(整型或浮点型)。除目标类型为_Bool 之外,结果始终为正值(或无符号)零,在这种情况下,布尔转换规则适用。

任何实数类型的值都可以隐式转换为任何虚数类型。结果总是一个正的虚构的零。

复数 - 虚数转换

任何虚构类型的值都可以隐式转换为任何复杂类型。

  • 结果的实际部分是正的零
  • 结果的虚数部分遵循相应实际类型的转换规则

任何复杂类型的值都可以隐式转换为任何虚构类型。

  • 真正的部分被丢弃
  • 结果的虚数部分遵循相应实际类型的转换规则
double imaginary z = I * (3*I); // the complex result -3.0+0i loses real part, gives zero

指针转换

一个指针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*转换为0的整型指针表达式都可以隐式转换为任何指针类型(既指向对象又指向函数的指针)。结果是它的类型的空指针值,保证比较不等于该类型的任何非空指针值。这个整数或 void *表达式被称为空指针常量,标准库提供这个常量的一个定义作为宏NULL

int* p = 0;
double* q = NULL;

笔记

尽管任何算术运算符中的有符号整数溢出都是未定义行为,但在整数转换中溢出有符号整数类型仅仅是未指定的行为。

另一方面,尽管任何算术运算符(和整数转换)中的无符号整数溢出是一个定义明确的操作,并遵循模算术规则,但浮点到整数转换中的无符号整数溢出是未定义的行为:可以转换为无符号整数的实际浮动类型的值是来自打开间隔(-1; Unnn_MAX + 1)的值。

unsigned int n = -1.0; // undefined behavior

指针和整数之间的转换(除了指向_Bool 的指针和整数常量表达式,值为零到指针)之间,指向对象的指针(除非指向 void 的指针或指向 void 的指针之外)以及指向函数的指针之间的转换函数具有兼容类型)从不隐含,并且需要一个演员操作符。

在指向函数的指针和指向对象的指针(包括 void *)或整数之间没有转换(隐式或显式)。

参考

  • C11 standard (ISO/IEC 9899:2011):
    • 6.3 Conversions (p: 50-56)
  • C99 standard (ISO/IEC 9899:1999):
    • 6.3 Conversions (p: 42-48)
  • C89/C90 standard (ISO/IEC 9899:1990):
    • 3.2 Conversions

C 语法相关

1.#define directive
2.#elif directive
3.#else directive
4.#endif directive
5.#error directive
6.#if directive
7.#ifdef directive
8.#ifndef directive
9.#include directive
10.#line directive
11.#pragma directive
12.alignas
13.Alternative operators and tokens
14.Analyzability
15.Arithmetic operators
16.Arithmetic types
17.Array declaration
18.Array initialization
19.ASCII Chart
20.Assignment operators
21. types
22.Basic concepts
23.Bit fields
24.break statement
25.C language
26.C Operator Precedence
27.cast operator
28.character constant
29.Comments
30.Comparison operators
31.compound literals
32.Conditional inclusion
33.Conformance
34.const type qualifier
35.Constant expressions
36.continue statement
37.Declarations
38.do-while loop
39.Enumerations
40.Escape sequences
41.Expressions
42.External and tentative definitions
43.File scope
44.floating constant
45.for loop
46.Function declarations
47.Function definitions
48.Functions
49.Generic selection
50.goto statement
51.Identifier
52.if statement
53.Increment/decrement operators
54.Initialization
55.inline function specifier
56.integer constant
57.Lifetime
58.Logical operators
59.Lookup and name spaces
60.Main function
61.Member access operators
62.Memory model
63.Objects and alignment
64.Order of evaluation
65.Other operators
66.Phases of translation
67.Pointer declaration
68.Preprocessor
69.restrict type qualifier
70.return statement
71.Scalar initialization
72.Scope
73.sizeof operator
74.Statements
75.static assert declaration
76.Static storage duration
77.Storage-class specifiers
78.string literals
79.Struct and union initialization
80.Struct declaration
81.switch statement
82.Thread storage duration
83.Type
84.Type
85.Typedef declaration
86.Undefined behavior
87.Union declaration
88.Value categories
89.Variadic arguments
90.volatile type qualifier
91.while loop
92._Alignof operator
93._Noreturn function specifier
C

C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。