安卓调用c语言函数,Android NDK 4 C语言函数和变量

前言

将程序分成适当的自包含单元是开发任意程序的基本方式。在开发时,应该将其分成多个便于管理的部分,这样带来的好处显而易见,程序的结构更加清晰,更容易复用、维护和测试,同时也更加利于团队开发。而在 C 语言中有一个重要的观念,那就是每个程序都应切割成许多小的函数。

一、变量的作用域和生存期

变量只存在于定义它们的块中,它们在声明时创建,在遇到代码块结束符时就不存在了。变量的存在时间被称为变量的生存期。看下面这个例子:

{

int a = 0;

// 在这里可以访问变量 a,不可以访问变量 b,因为此时变量 b 还没有被创建

{

int b = 0;

// 这里可以访问变量 a 和 b

}

// 在这里可以访问变量 a,但是不可访问变量 b,以为这里变量 b 已经超出了它

// 的声明周期范围,被销毁了

}

执行程序时,会创建变量,并给它分配内存。关于内存分配的详情请参考《深入理解计算机操作系统》的二部分 —— “在系统上运行程序”中的相关内容,这里不再展开。

二、函数

2.1、函数定义

C 语言定义一个函数包含两个部分:

函数头:函数头定义了函数的名称、函数参数以及函数的返回类型;

函数体:函数体包含在调用函数时执行的语句,以及对传给它的参数值执行操作的语句。

函数的一般形式如下:

Return_type Function_name(Paramters - separated by commas)

{

// Statements...

}

函数体内可以没有语句,但是必须有大括号。如果函数体内无语局,那么返回类型必须是 void,void 类型表示“不存在任何类型”,所以在这里表示函数没有返回值。

对于函数头中的参数这里有两个术语要注意:

“参数”:表示函数定义中的一个占位符,指定了调用函数时传送给函数的值的类型。参数用来表示函数执行时使用的数据类型和名称;

“变元”:表示调用函数时提供的对用于参数的值。

函数的参数在函数头中定义,是调用函数时必须指定的变元的占位符。参数提供了调用函数给被调用函数传递信息的方法。这些参数名对于函数而言是本地的,调用函数时给它们指定的值称为“变元”。然后使用这些参数名在函数体中执行相应的语句,当函数执行时,参数使用变元的值。

这里要注意的是,要把数组作为变元传递给函数,还必须传递一个额外的变元 —— 数组大小,不然函数无法确定数组中元素的个数,这个会在后面的“按值传递机制”中进行详细解释。

下面接着说 return 语句,return 语句允许函数退出,从调用函数中“发生调用”的那一点继续执行。return 语句最简单的形式如下:

return;

这个形式的 return 语句用于返回类型声明为 void 的函数,表示不返回任何值,较常用的 return 语句形式如下:

return expression;

2.2、按值传递机制

给函数传递变元时,变元值不会直接传递给函数,而是先创建变元值的副本,存储在栈上,函数中使用的创建的个副本,而不是使用变元的初始值。这个机制是 C 语言中所有变元值传递给函数的方式,称之为按值传递机制(pass-by-value mechanism)。

被调用函数修改属于调用函数的变量值的惟一方式是 —— 把变量的地址接收为变元值。给函数传递地址时,它只是所传递地址的一个副本,而不是初始的地址。但是,副本仍是一个地址,仍然引用最初的变量。这就是必须把变量的地址传递给 scanf() 的原因。

需要注意的是,把数组传递为变元时,按值传递的一个有趣结论是:数组名本身引用了数组的启始地址,但它不是指针,不能修改这个地址(常量指针)。把数组名用作变元时,会制作该地址的副本,并将地址传递给函数。该副本现在只是一个地址,所以被调用函数可以用任意方式修改这个地址(最初的数组地址不受影响)。但这不是推荐的方式。示例代码:

double sum(double x[], size_t n) {

double sum = 0.0;

for(size_t i = 0; i < n; ++i)

// sum += x[i];

sum += *(x++); // 修改副本

return sum;

}

以上函数第一个参数类型为数组类型,所以对应的变元可以是数组名或是 double* 类型的指针。

注意,从函数返回的值也是一个副本。这是必需的,因为函数体内定义的自动变量和其他本地变量都会在函数返回时删除。

2.3、函数原型

先看以下代码:

int main() {

// Code in main() ...

}

double average(double x, size_t n) {

return sum(x,n)/n;

}

double sum() {

// Statements ...

}

size_t getData(double *data, size_t max_count) {

// Statement ...

}

以上代码不会被编译。编译器在遇到 average() 函数的调用时,不知该如何处理,因为那时 average() 函数还未声明。函数声明也称为函数原型,是一个定义函数基本特性的语句,它定义了函数的名称、返回值的类型和每个参数类型。

在程序中包含头文件时,这个头文件就会在程序中为库函数添加函数原型。例如,头文件 中含有 printf()和 scanf() 的函数原型。

在以上示例代码中将 average()、sum()、getData() 在 mian() 函数之前声明即可编译已成功。在技术上可以将 average() 函数的声明放在 main() 函数中,只是 average() 函数的声明必须在调用该函数之前,但事实上该做法并不可行。函数原型一般放在源文件的开头处,而且在所有函数的定义和头文件之前。

另外,在源文件中,函数原型在所有函数的外部。因此无论函数的定义放在什么位置,源文件中的任何函数都可以调用该文件中的其他函数。

在函数原型中可以不包含参数名,这是可行的,但是不推荐。注意参数类型 double* 等价与函数定义中的参数类型 double[]。

2.4、指针用作参数和返回值

在把数组作为变元,通过指针参数传递给函数时,只是传递了该数组的地址副本,而并没有传递数组。被调用函数需要知道数组的个数,这有两种方式:

定义一个额外的参数,即数组的个数。这个机制在其他编程语言(如 C++)中用的很多。

在函数可以访问的最后一个数组中存储一个特别的唯一值。这个机制用于字符串,表示字符串的 char数组在最后一个元素中存储了'\0'。该机制有时也可以应用在其他类型数据的数组。

2.5、常量参数

可以使用 const 关键字修饰函数参数,这表示函数将传递给参数的变元看作一个常量。由于变元是按值传递的,所以只有参数是一个指针时,这个关键字才有效。一般将 const 关键字应用于指针参数,指定函数不能修改该变元指向的值。示例代码如下:

bool sendMessage(const char* pMessage) { // 指向常量的指针,表示该指针指向的值不能被修改

// Code to send the message

return true;

}

参数 pMessage 的类型是指向 const char 的指针(指向常量的指针)。也就是说不能修改的是 char 值,而不是地址。编译器将确认函数体中没有使用 pMessage 指针修改原 char 值。将指针参数指定为 const 有另一个用途,const 修饰符暗示,函数不能修改指针指向的数据,因此编译器知道,指向常量数据的指针变元是安全的。

另一方面,如果不给参数使用 const 修饰符,对编译器而言,函数就可以修改变元指向的数据。将指向常量数据的指针作为变元传送给未声明为 const 的参数时,C 编译器至少应给出一条警告消息。

提示:如果函数不修改指针参数指向的数据,就应该将该函数参数声明为 const。

当参数是指向指针的指针时,传递给该参数的变元是按值传递的,所以无法把指针指定为 const (指定为 const 无意义,可修改副本值)。但是,可以把指针指向的指针定义为 const,防止修改指针指向的内容。但我们仅希望最终被指向的数据是 const。

对于指针的指针参数,下面是 const 一种可能的用途:

void short(const char** str, size_t n);

这是 sort() 函数的原型,其第一个参数是指向 const char 的指针的指针类型。把第一个参数看作一个字符串数组,则字符串本身是常量,它们的地址和数组的地址都不是常量。该函数会重新安排在数组中存储的地址,而不修改它们指向的字符串。

第二种可能用途:

void replace(char *const *str, size_t n);

这里,第一个参数是指向 char 的常量指针的指针。变元是一个字符串数组,函数可以修改字符串,但是不能修改数组中的地址。例如,函数可以用空格替换标点符号,但是不能重新安排字符串在数组中的顺序。

最后一中可能用途:

size_t max_length(const char* const* str, size_t n);

在这个函数原型中,第一个参数是指向 const char 的常量指针的指针类型。数组中的指针是常量,它们指向的字符串也是常量。该函数可以访问数组,但是不能以任何方式修改数组。该函数一般返回字符串的最大长度,获得这个数据时不会修改任何内容。

2.6、返回指针的风险

函数的返回值,实际上返回的是该值的一个副本。从函数中返回指针是一个非常强大的功能,因为它允许返回一整组值,而不是仅仅返回一个值。返回指针有一些特定的风险,看下面这个例子:

#include

long *incomePlus(long* pPay);

int main(void) {

long your_pay = 30000L;

long *pold_pay = &your_pay;

long *pnew_pay = NULL;

pnew_pay = incomePlus(pold_pay);

printf("Old pay = $%ld\n", *pold_pay);

printf("New pay = $%ld\n", *pnew_pay);

return 0;

}

long *incomePlus(long* pPay) {

long pay = 0;

pay = *pPay +1000;

return &pay;

}

在编译时,会得到一下警告:

warnning:function returns address of local variable

这是因为该程序返回了变量 pay 的地址,但是在退出函数 onconePlus() 时,它就超出了作用域,这是很容易犯的错误,如果编译器没有提出警告,就很难找出这个错误。

定律:绝不返回函数中本地变量的地址

三、函数指针

指针也可以操作函数,函数的内存地址存储了函数开始执行的位置(启始位置),存储在函数指针中的内容就是这个地址。不过仅有地址还不够,如果函数通过指针来调用,还必须提供变元的类型和个数,以及返回类型。编译器不能仅仅通过地址就能推断出以上信息,这就意味着声明函数指针比声明数据类型指针要复杂。指针包含了地址,所以必须定义一个类型;同样,函数指针也包含地址,因此也必须定义一个原型。

3.1、声明函数指针

以下代码是一个声明函数指针的简单示例:

int (*pFunction) (int);

以上代码是一个函数指针变量的声明,它不指向任何内容 —— 该语句只定义了指针变量。这个指针的名称为 pFunction,指向一个参数是 int 类型、返回值是 int 类型的函数。而且,这个指针只能指向有这些特征的指针。

3.2、通过函数指针调用函数

定义以下函数原型:

int sum(int a, int b);

可以把该函数的地址存储在声明如下的函数指针中:

int (*pFun) (int, int) = sum;

这条语句声明了一个函数指针 pFun,它存储函数的地址,该语句还将 sum() 函数的地址初始化 pFun。要提供初始值,只需要使用有所需原型的函数名。

接下来就可以通过函数指针来调用 sum() 函数:

int result = pFun(45,55);

注意,像使用函数那样使用函数指针名调用该指针指向的函数,不需要使用解引用运算符。

假定定义了有如下原型的另一个函数:

int product(int a, int b);

就可以使用以下语句在 pFun 中存储 product() 的地址:

pFun = product;

3.3、函数指针和数组

函数指针和一般变量是一样的,所以可以创建函数指针的数组。要声明函数指针数组,只需要将数组的大小放在函数指针数组名之后。如下所示:

int (*pFunctions[10]) (int);

以上语句声明了一个包 10 个元素的 pFunctions 数组。该数组中每个元素都能存储一个函数的地址,该函数有两个 int 类型的参数,返回类型为 int。示例代码如下:

#include

int sum(int, int);

int product(int, int);

int difference(int, int);

int main(void) {

int a = 10;

int b = 5;

int result = 0;

int (*pFuns[3]) (int, int);

pFuns[0] = sum;

pFuns[1] = product;

pFuns[2] = difference;

for(int i = 0; i < 3; ++i) {

result = pFuns[i](a,b);

printf("result = %2d\n", result);

}

}

int sum(int x, int y) {

return x + y;

}

int product(int x, int y) {

return x * y;

}

int difference(int x, int y) {

return x - y;

}

还可以使用以下初始化赋值语句对函数指针数组进行赋值:

int (*pFuns[3]) (int, int) = {sum, product, difference};

int (*pFuns[]) (int, int) = {sum, product, difference};

3.4、作为变元的函数指针

也可以将函数指针作为变元来传递,这样就可以根据指针所指向的函数而调用不同的函数了。示例代码如下:

int sum(int, int);

int product(int, int);

int difference(int, int);

// 将函数指针作为变元的函数原型

int any_function(int (*pfun) (int, int), int x, int y);

int main(void) {

int a = 10;

int b = 5;

int(*pf)(int, int) = sum;

result = any_function(pf, a, b);

result = any_function(product, a, b);

}

int any_function(int (*pfun) (int, int), int x, int y) {

return pfun(x, y);

}

指针 pf 用作变元,pf 的初始值是函数 sum() 函数的地址,所以在 any_function() 中调用了 sum() 函数;any_function() 的下一个调用如下:

result = any_function(product, a, b);

这里指定函数名 product 作为第一个变元,所以在 any_function()中调用函数 product()。此例中,编译器会创建一个指向 product() 函数的内部指针,并传给函数 any_function()。

四、函数中的变量

4.1、静态变量:函数内部的追踪

前面使用的所有变量在执行到定义它的尾块时就超出了作用域,它们在栈上分配的内存会被释放,一共另一个函数使用。这些变量称为自动变量,它们在声明时自动创建,在程序退出它的块后自动销毁。

然而在某些情况下,要求在退出一个函数调用后,该函数中的数据可以在程序的其他函数中使用。例如函数中的某种计数器,这使用自动变量是做不到的。C 语言提供了静态变量,可以达到该目的。声明一个静态变量的简单示例如下:

static int count = 0;

这里使用关键字 static,该语句声明的变量和自动变量有两点不同:

虽然在函数的作用于中声明,但是在函数退出后,这个静态变量不会被销毁。

自动变量每次进入作用域时,都会初始化一次,但是声明为 static 的变量只在程序的开始时初始化一次。

静态变量只能在包含其声明的函数中可见,但它是一个全局变量,因此可以用全局变量的方式使用它。只要程序开始执行,静态变量就一直存在,但是它只能在声明它的范围内可见,不能在该作用域的外部引用。

4.2、在函数间共享变量

可以在所有函数间共享变量,常量在程序文件的开头声明,所以常量位于组成程序的所有函数的外部。同样也可以使用这种方式声明变量,这种变量称为全局变量,全局变量的声明方式与一般变量相同,但它声明的位置非常重要,这个位置确定了变量是否为全局变量。

示例代码:

#include

// 全局变量(如果未初始化,默认值为0),从声明该变量到程序结束的任

// 何位置都可以访问它

int count = 0;

void test1(void);

void test2(void);

int main() {

// 自动变量(与全局变量同名,本地变量隐藏了全局变量)

int count = 0;

for(; count < 5; ++count) {

test1();

test2();

}

return 0;

}

void test1(void) {

// 使用全局变量

printf("test1 count = %d\n", ++count);

}

void test2(void) {

// 静态变量,默认初始化为0(隐藏了同名的全局变量)

static int count;

printf("test2 count = %d\n", ++count);

}

全局变量可以取代函数变元及返回值,完全取代自动变量似乎很吸引人,但是应该少时用全局变量。

注意:在 C 语言中,最好不要将本地变量和全局变量使用相同的名称。

五、调用自己的函数:递归

递归在程序设计中不常见,不过在某些情况下,这是一个效率很高的技巧,可以显著简化解决特定问题所需的代码;在树的前序、中序,后序遍历算法中,递归的实现明显比循环简

单。递归的坏处主要有以下几点:

递归就是调用函数自身,而函数调用是有时间和空间的消耗;—— 效率

递归中很多计算都是重复的,有很多子问题存在相互重叠的部分;—— 效率

调用栈可能会溢出。—— 性能

在一些编译器里会将递归优化为尾递归,也就是将递归转化为迭代,在 Kotlin 的编译器中就会进行这种优化。

六、变元个数可变的函数

在标准库中,某些变元的数量是可变的,例如函数 printf() 和 scanf()。标准库 提供了编写这种函数的例程。

6.1、指定函数原型

定义以下变元可变函数原型,计算两个或多个 double 的平均值,其原型如下:

double average(double q, double b, ...);

第二个参数后面的省略号表示,在前两个固定的变元后面可以有数量可变的变元(至少要有一个固定的变元)。

6.2、编写函数时如何引用变元

由于不知道变元的个数,所以不能给他们指定名称,惟一的方法是通过指针间接地指定变元。 为此提供了通常实现为宏的例程,宏的外观和操作都类似于函数,所以将它们作为函数来讨论。要实现变元个数可变的函数,必须同时使用 3 个宏:

va_start()

va_arg()

va_end()

第一个宏的形式如下:

void va_start(va_list parg, last_fixed_arg);

该宏的名称来源于 variable argument start。这个函数接收两个变元:va_list 类型的指针 parg 和为函数指定的最后一个固定参数的名称。va_list 类型也在 头文件中定义,用于存储支持可变参数列表的例程所需的信息。

以 average() 函数为例,可以将该函数编写成:

double average(double v1, double v2, ...) {

va_list parg; // Pointer for variable argument list

// More code to go here ...

va_start(parg, v2);

// More code to go here ...

}

首先声明一个 va_list 类型的变量 parg。然后用 parg 作为第一个变元,指定最后一个固定参数 v2 作为第二个变元,调用 va_start()。调用 va_start() 的结果是将变量 parg 设定为指向传递给函数的第一个可变变元。此时并不知道该值的类型,标准库对此无能为力。但是必须确定每一个可变变元的类型,例如假设所有的可变变元都使用同一种特定的类型,或从固定变元包含的信息中推断出每个变元的类型。

average() 函数处理 double 类型的变元,所以确定变元的类型不成问题。接下来就必须知道如何访问每个变元的值,因此下面完成 average() 函数,示例代码如下:

#include

#include

double average(double v1, double v2, ...);

int main(void){

double v1 = 1.0;

double v2 = 2.0;

printf("average is %.2lf\n", average(1.0,2.0,3.0,4.0,5.0));

printf("average is %.2lf\n", average(1.0,2.0,3.0,4.0,0.0));

return 0;

}

double average(double v1, double v2, ...) {

va_list parg;

double sum = v1 + v2;

double value = 0.0;

int count = 2;

va_start(parg, v2);

while((value = va_arg(parg, double)) != 0.0) {

sum += value;

++count;

}

va_end(parg);

return sum/count;

}

在循环条件 while((value = va_arg(parg, double)) != 0.0) 调用了 头文件中的另一个函数 va_arg()。va_arg() 的第一个变元是通过调用 va_start() 初始化的变量 parg,第二个变元时期望确定的变元类型的说明。va_arg() 返回 parg 指定的当前变元值,并肩它存储到 value 中。同时会更新 parg 指针,是指根据调用中指定的类型,指向列表中下一个变元。必须有某种方式来确定可变变元的类型,如果类型信息不正确,就不能得到下一个变元。

调用 va_end() 函数,处理该过程的剩余工作。它将 parg 重置为指向 NULL。如果省略掉这个调用,程序就不能正常工作。以上代码输出如下:

average is 3.00

average is 2.50

6.3、复制 va_list

有时需要多次处理可变的变元列表。 头文件为此定义了一个复制已有 va_list 的例程。假定在函数中使用 va_start() 创建并初始化了一个 va_list 对象 parg,\现在要复制 parg:

va_list parg;

va_start(parg,v2);

va_list parg_copy;

va_copy(parg_copy, parg)

函数 va_copy() 将 parg 的内容复制到 parg_copy 中。接着就可以独立的处理 parg 和 parg_copy,使用 va_arg() 和 va_end() 提取变元值。需要注意的是:copy() 例程赋值 va_list 对象时,不需要考虑它所处的状态;另外,在对 parg_copy 执行 pa_end() 之前,不能将 va_list 对象 parg_copy 用作另一个复制过程的目标。

长度可变的变元列表的基本规则可以总结如下:

在变元数量可变的函数中,至少要有一个固定变元;

必须调用 va_start() 初始化函数中可变变元列表指针的值。变元指针的类型必须声明为 va_list 类型;

必须有确定每个变元类型的机制;

必须有确定何时终止变元列表的方法;

va_arg() 的第二个变元指定了可变变元值的类型,这个指针类型可以在类型名的后面加上 * 来指定。

在退出变元数目可变的函数前,必须调用 va_end()。

七、main() 函数

main() 函数就是程序执行的起点。该函数有一个参数列表,在命令行中执行程序时,可以给它传递变元。main() 函数可以有两个参数,也可以没有参数。

main() 函数有参数时,第一个参数类型为 int,表示在命令行中执行 mian() 函数的参数的个数,包含程序名在内。第二个参数类型为字符串指针数组。因此,如果在语句执行中,在程序名称的后面添加两个变元,那么 main() 函数的第一个变元的值就是 3,第二个参数是一个包含 3 个指针的数组,第一个指针指向程序的名称,第二和第三个指针指向在命令行中输入的两个变元。

示例代码:

#include

int main(int argc, char* argv[]) {

printf("Program name is %s\n", argv[0]);

for(int i = 0; i < argc; ++i) {

printf("Argument %d: %s\n", i, argv[i]);

}

return 0;

}

命令行指令:

Chapter9_pFunction$ ./main 1 2

输出:

Program name is ./main

Argument 0: ./main

Argument 1: 1

Argument 2: 2

所有命令行变元都以字符串读入,如果在命令行中输入数值,就需要把包含数值的字符串转换为适当的数值类型。可以使用以下函数,这些函数在 头文件中声明。

函 数

说 明

atof()

将作为变元传送的字符串转换为 double 类型

atoi()

将作为变元传送的字符串转换为 in 类型

atol()

将作为变元传送的字符串转换为 long 类型

atoll

将作为变元传送的字符串转换为 long long 类型

如果将一个命令行变元用作整数,示例代码如下:

int arg_value = 0;

if(argc > 1) arg_value = atoi(argv[1]);

八、结束程序

有时需要结束程序的执行,在 main() 函数中可以返回以结束程序。但在其他函数中不会使用该技术。在其他函数中结束程序可以使正常结束或是不正常结束。 头文件中提供的几个函数可以用于终止程序的执行;并且还提供了一些函数,标识出在程序正常结束时要调用的一个或多个自定义函数。

8.1、abort() 函数

调用 abort() 函数会不正常地结束程序。它不需要参数,也没有返回值。希望结束时可以调用它:

abort();

该函数会清空输出缓冲区,关闭打开的流,但它是否这么做取决于实现代码。

8,2、exit() 和 atexit() 函数

调用 exit() 函数会正常结束程序。该函数需要一个 int 类型的参数,它表示结束时的状态。该参数可以是 0 或者表示成功结束的 EXIT_SUCCESS,它会返回给主机环境。

例如:

exit(EXIT_SUCCESS); // 正常结束

如果变元是 EXIT_FAILURE,就把表示终止不成功的信息返回给主机环境。无论成功与否 exit() 都会清空所有输出缓冲,把它们包含的数据写入目的,在关闭所有打开的流,之后把控制权交给主机。

注意:调用 exit() 会正常结束程序,无论变元的值是什么。调用 atexit(),可以注册由 exit() 调用的自定义函数。

调用 atexit() 函数会标识应用程序终止时要执行的函数。以下是示例代码:

void CleanUp(void);

...

if(atexit(CleanUp)) printf("Registeration of function failed!\n");

把要调用的函数名作为变元传递给 atexit(),如果注册成功,就返回 0,否则返回非 0 值。注册的函数最多为 32 个,把几个函数注册为调用 exit() 时执行,它们就在程序中止时已注册的顺序的倒序调用。

示例代码:

#include

#include

void registerExit(void);

void exit1(void);

void exit2(void);

void exit3(void);

int main(void) {

registerExit();

exit(0);

}

void exit1(void) {

printf("exit1\n");

}

void exit2(void) {

printf("exit2\n");

}

void exit3(void) {

printf("exit3\n");

}

void registerExit(void) {

if(atexit(exit1)) printf("Registeration of function failed!\n");

if(atexit(exit2)) printf("Registeration of function failed!\n");

if(atexit(exit3)) printf("Registeration of function failed!\n");

}

输出:

/BeginningC/Chapter9_pFunction$ ./exit

exit3

exit2

exit1

8.3、_Exit() 函数

_Exit() 函数的作用与 exit() 相同,区别是:它无法影响程序终止时调用 _Exit() 函数的结果,因为它不调用任何已注册的函数。

8.4、quick_exit() 和 at_quick_exit() 函数

调用 quick_exit() 会正常终止程序,在调用 _Exit() 把控制权返回给主机环境。quick_exit() 的变元是一个 int 类型的状态码,该函数在调用 _Exit() 时传递该变元。在调用_Exit() 之前,quick_exit() 会调用通过 at_quick_exit() 函数调用注册的函数。下面把函数注册为由 quick_exit() 调用:

void CloseFiles(void);

void CloseCommunicationsLinks(void);

...

at_quick_exit(CloseCommunicationsLinks);

at_quick_exit(CloseFiles);

最后两个语句把函数注册为由 quick_exit() 调用,于是先调用 CloseFiles(),在调用 CloseCommunicationsLinks()。

quick_exit() 函数提供了与 exit() 平行的程序终止机制。注册为由 exit() 和 quick_exit() 调用的函数完全相互独立。

九、提高性能

9.1、内联函数

C 语言的功能结构要求将程序分解为很多函数,函数有时可以非常短。短函数的每次调用可以用实现该函数功能的内联函数代替,以提高执行性能。这就意味着,不需要给函数传递值或是返回值。

要让编译器把短函数指定为 inline,示例代码如下:

inline double bmi(double kg_wt, double m_height) {

return kg_wt/(m_height*m_height)

}

这个操作可以定义为一个函数,也可以使用调用的内联实现方式。要采用后一种方式,需要在函数头中使用 inline 关键字来指定。但是一般不保证编译器能够使别声明为 inline 的函数,因为关键字对编译器来说这是一个提示。

9.2、使用 restrict 关键字

专业的 C 编译器可以优化对象代码的性能,这涉及到改变在代码中为操作指定的计算顺序。为了优化代码,编译器必须确保这种重新排序不影响计算结果,并用指针指出这方面的错误。

为了优化涉及指针的代码,编译器必须能确定指针是没有别名的 —— 也就是说,每个指针引用的数据项都没有在给定的范围内以其他方式引用。关键字 restrict 可以告诉编译器,何时出现该状况,并允许应用代码优化功能。以下是在 中声明的函数示例:

errno_t strcpy_s(char * restrict s1, r_size slmax, const char * restrict s2) {

// ...

}

以上函数将 s2 复制到 s1 中。关键字 restrict 应用于两个指针参数,表示在函数中,s1 和 s2 引用的字符串仅通过这两个指针引用,所以编译器可以优化生成的代码。关键字 restrict 仅将信息告知编译器,但不保证进行优化。在条件不具备的代码上应用关键字 restrict,代码会生成不正确的结果。

大多数情况下,不需要使用 restrict 关键字,只有代码进行大量计算,进行代码优化才有显著的效果,而这还取决于编译器。

9.3、_Noreturn 函数限定符

有时函数永远都不会返回,例如定义这样一个函数,在程序正常终止时调用。这种函数不会返回,因为控制权会像通常那样返回给调用者,此时,可以告诉编译器,该函数不返回:

_Noreturn void EndAll(void) {

exit(EXIT_SUCCESS);

}

_Noreturn 函数限定符告诉编译器,这个函数不返回给其调用函数。因为函数不返回,所以唯一可用的返回类型为 void。 头文件定义了宏 noreturn,它扩展为_Noreturn,所以只要在源文件中包含这个头文件,就可以使用 noreturn。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值