6.9.1 函数定义
语法
1、function-definition:
declaration-specifiers declarator declaration-listopt compound-statement
declaration-list:
declaration
declaration-list declaration
约束
2、在一个函数定义中所声明的标识符(它是函数名)应该是一个函数类型,它通过函数定义的声明符部分指定。[注:这样的目的是在一个函数定义中的类型类别不能从一个typedef继承:
typedef int F(void); // 类型F是一个“返回int,不带有形参的函数” F f, g; // f和g都具有与F相兼容的类型 F f { /* ... */ } // 错误:语法/约束错误 F g() { /* ... */ } // 错误:将g声明为返回一个函数 int f(void) { /* ... */ } // 正确:f具有与F相兼容的类型 int g() { /* ... */ } // 正确:g具有与F相兼容的类型 F *e(void) { /* ... */ } // e返回指向一个函数的指针 F *((e))(void) { /* ... */ } // 与上述相同,不用去管圆括号 int (*fp)(void); // fp指向具有类型F的一个函数 F *fp; // fp指向一个具有类型F的函数
]
3、一个函数的返回类型应该是void或一个完整的对象类型,但不能是数组类型。
4、如果存在存储类说明符,那么在声明说明符中应该要么是extern,要么是static。
5、如果声明符包含一个形参类型列表,那么每个形参的声明应该包含一个标识符,除了一个形参列表由一单个void类型形参构成的特殊情况,在这种情况下,不应该有一个标识符。后面不应该跟着任何声明列表。
6、如果声明符包含了一个标识符列表,那么在声明列表中的每个声明应该至少具有一个声明符,那些声明符应该尽从声明列表声明标识符,并且标识符列表中的每个标识符应该被声明。被声明为一个typedef名的一个标识符不应该重新声明为一个形参。在声明列表中的声明不应该包含除了register之外的其它存储类说明符,并且不能包含初始化。
语义
7、一个函数定义中的声明符指定了正被定义的函数名以及其形参标识符。如果声明符包含一个形参类型列表,那么该列表也指定了所有形参的类型;这么一个声明符也担任一个函数原型为稍后对同一函数的调用,在同一翻译单元中。如果声明符包含了一个标识符列表,[注:见“未来语言方向”(6.11.7)。]形参的类型应该在后续声明列表中被声明。在任何一种情况下,一个形参类型列表的每个形参的类型被调整为6.7.6.3中描述的;结果类型应该是一个完整的对象类型。
8、如果接受一可变数量实参的函数定义时没有一个用省略号记号结尾的形参类型列表,那么行为是未定义的。
9、每个形参具有自动存储周期;其标识符是一个左值。[注:一个形参标识符不能在函数体内被重新声明,除了在一个封闭的语句块内。]形参存储的布局是未定义的。
10、在函数入口处,每个可变修改的形参的size表达式都被计算,并且每个实参表达式的值被转换为相应形参的类型,就好比是一个赋值操作。(作为实参的数组表达式与函数指派符在调用之前被转换为指针。)
11、在所有形参被赋值之后,由函数定义体所构成的复合语句被执行。
12、如果终结一个函数的}到达,那么行为是未定义的。
13、例1 在下列代码中:
extern int max(int a, int b) { return a > b ? a : b; }
extern是存储类说明符,而int是类型说明符;max(int a, int b)是函数声明符;{ return a > b ? a : b; } 是函数体。以下类似的定义使用了标识符列表形式给形参声明:
extern int max(a, b) int a, b; { return a > b ? a : b; }
这里int a, b;是形参的声明列表。这两个定义的不同之处是第一种形式扮演了一个原型声明,它迫使后续对此函数的调用对实参类型进行转换,而第二种形式则不然。
14、例2 为了将一个函数传递给另一个,有人会说
int f(void); /* ... */ g(f);
然后对g的定义可能写作
void g(int (*funcp)(void)) { /* ... */ (*funcp)(); /* 或funcp(); ... */ }
或者等价地
void g(int func(void)) { /* ... */ func(); /* 或(*func)(); ... */ }
本结完。