专栏名称: 嵌入式微处理器
关注这个时代最火的嵌入式微处理器,你想知道的都在这里。
目录
相关文章推荐
上海证券报  ·  筹划控制权变更,明起停牌 ·  昨天  
上海证券报  ·  微信+DeepSeek,又有大动作 ·  3 天前  
南方能源观察  ·  德国全面推行动态电价,激励灵活用电 ·  4 天前  
51好读  ›  专栏  ›  嵌入式微处理器

C语言函数调用:关于错误码和返回值传递的几个小思考

嵌入式微处理器  · 公众号  ·  · 2024-06-04 17:59

正文

C 语言 是一门 面向过程 的编程语言,通过一个又一个函数,把计算、过程控制等逻辑,包装成一个个独立的处理单元。

既然是函数调用,就一定会有 参数和返回值 的传递问题,因此也就产生了多种不同的编程范式,比如:

  1. Posix 风格:函数返回值只用来表示成功(0)或失败(非0),其他的输出结果都使用参数来传递。

  2. Unix 风格:函数返回值即包括错误代码,也包括有用的输出结果。

  3. GAI 风格:与 Posix 有点类似,函数执行成功时返回0,否则就返回非0。

今天,我们就来聊一聊这些 函数调用范式 在开发过程中的一些小思考。

假设有一个算法函数, 输入 两个整型参数, 输出 一个整型结果,并且 输出 一个错误代码。

第一种:输入、输出结果和错误码全部通过参数传递

既然所有的信息都是通过 参数 来传递的,那么函数定义就应该是下面这样:

void func1(int a, int b, int *result, int *err_code)
{
int c = a + b;
*result = c;
err_code = 0; // 沿用 Linux 中的习惯,0 表示没有发生错误。
}

因为 不需要 返回任何数据,因此函数签名的返回类型就是 void

因为调用者需要获取 输出结果和错误码 ,因此在形参中, result err_code 需要传递指针类型的变量。

面对这样的函数签名,调用者就必须显示的定义两个变量 result err_code ,用来 接收 函数的输出。

// 调用者代码

int result, err_code;
func(1, 2, &result, &err_code);
if (0 == err_code)
printf("Success. result = %d \n", result);
else
printf("Failed. err_code = %d \n", err_code);

这种函数范式的 优点 就是:在调用形式上统一,无论参数类型是什么(基础类型、结构体等待),都是整齐划一的函数调用写法。

缺点就是有点 累赘

面对任何一个函数,调用者都必须定义一个 err_code 变量传递进去。

如果一个函数是过程控制类型的,压根就不会产生什么错误码,这样的函数调用就显得很臃肿,因为调用者压根就 不需要 检查错误码。

第二种:函数返回值表示错误码

也就是把第一种方式中的 err_code 参数,通过函数 返回值 赋值给调用者。

这种函数编程范式还是比较常见的, 返回值只表示错误码,其他的输出结果都通过参数引用(指针)来传递。

int func2(int a, int b, int *result)
{
int c = a + b;
*result = c;
return 0; // 返回错误码
}

这样的函数范式跟 POSIX 风格很像了。

面对这样的函数,调用者的写法就会变成这样:

// 调用者代码

int result, err_code;
err_code = func2(1, 2, &result);
if (0 == err_code)
printf("Success. result = %d \n", result);
else
printf("Failed. err_code = %d \n", err_code);

看起来好像跟第一种方式没有什么本质区别,但是再看一下下面这样的写法呢:

// 调用者代码

int result;
if (0 == func2(1, 2, &result))
printf("Success. result = %d \n", result);
else
printf("Failed.\n");

这样的代码风格,在 Linux 中是不是很常见?当不需要处理错误码时,这样的编程方式会 更方便 一些。

第三种:函数返回值表示输出结果

也就是把第一种方式中的 result 参数,通过函数返回值赋值给调用者。

int func3(int a, int b, int *err_code)
{
int c = a + b;
*err_code = 0;
return c;
}

这有点类似 Unix 中的风格:

  1. 返回结果中包括了有用的数据,但是它有一个局限:返回结果必须与错误码的类型一致。

  2. 另外还有一个问题:如果 int 型的返回结果也可能是负数, 所以 Unix 中还必须使用另一个全局变量 errno 来单独存储错误码,存在线程安全问题(可以使用线程局部存储来解决)。

面对这样的函数签名,调用者的调用方式如下:

// 调用者代码

int result, err_code;
result = func3(1, 2, &err_code))

if (0 == err_code)
printf("Success. result = %d \n", result);
else
printf("Failed.\n");

这种方式的 缺点 与第一种一样:必须定义一个变量 err_code, 来接收错误码。

在不必要检查错误码的场合中,显得有点多此一举。

小 结

以上的这三种函数调用方式, 没有好坏之分 ,只与每一位开发者的编码习惯有关系。

而且在实际的项目代码中,这三种方式都能看得到。

如果函数输出结果是结构体呢?

刚才讨论的三种方式中,函数输出结果 reuslt 是一个整型,如果它是一个 结构体类型 的变量,那么哪一种方式相对比较好呢?

这就要注意另外两点了:

  1. 结构体的赋值是需要时间开销的;

  2. 结构体赋值时,需要考虑深拷贝、浅拷贝的问题。







请到「今天看啥」查看全文