社区编辑申请
注册/登录
C语言函数调用:【错误码】和【返回值】传递的小思考
开发 后端
C 语言是一门面向过程的编程语言,通过一个又一个函数,把计算、过程控制等逻辑,包装成一个个独立的处理单元。

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

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

  • Posix 风格:函数返回值只用来表示成功(0)或失败(非0),其他的输出结果都使用参数来传递。
  • Unix 风格:函数返回值即包括错误代码,也包括有用的输出结果。
  • GAI 风格:与 Posix 有点类似,函数执行成功时返回0,否则就返回非0。

这篇文章就来轻松一下,聊一聊这些函数调用范式在开发过程中的一些小思考。

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

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

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

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

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

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

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

  1. // 调用者代码 
  2.  
  3. int result, err_code; 
  4. func(1, 2, &result, &err_code); 
  5. if (0 == err_code) 
  6.     printf("Success. result = %d \n", result); 
  7. else 
  8.     printf("Failed. err_code = %d \n", err_code); 

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

缺点就是有点累赘。

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

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

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

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

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

  1. int func2(int a, int b, int *result) 
  2.     int c = a + b; 
  3.     *result = c; 
  4.     return 0;   // 返回错误码 

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

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

  1. // 调用者代码 
  2.  
  3. int result, err_code; 
  4. err_code = func2(1, 2, &result); 
  5. if (0 == err_code) 
  6.     printf("Success. result = %d \n", result); 
  7. else 
  8.     printf("Failed. err_code = %d \n", err_code); 

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

  1. // 调用者代码 
  2.  
  3. int result; 
  4. if (0 == func2(1, 2, &result)) 
  5.     printf("Success. result = %d \n", result); 
  6. else 
  7.     printf("Failed.\n"); 

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

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

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

  1. int func3(int a, int b, int *err_code) 
  2.     int c = a + b; 
  3.     err_code = 0; 
  4.     return c; 

这有点类似Unix中的风格:

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

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

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

  1. // 调用者代码 
  2.  
  3. int result, err_code; 
  4. result = func3(1, 2, &err_code)) 
  5.  
  6. if (0 == err_code) 
  7.     printf("Success. result = %d \n", result); 
  8. else 
  9.     printf("Failed.\n"); 

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

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

小结

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

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

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

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

这就要注意另外两点了:

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

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

当看完以上几个小思考时,是不是觉得特别简单、不屑一顾?

不妨继续思考一步:在我们的实际编程过程中,是不是每次能够注意、遵守这些小细节问题呢?

如果团队中没有强制的代码规范,同事之间不会code review,我们是不是都会选择偷懒、放过自己呢?我就是^-^

本文转载自微信公众号「IOT物联网小镇」,可以通过以下二维码关注。转载本文请联系IOT物联网小镇公众号。

 

责任编辑:武晓燕 来源: IOT物联网小镇
相关推荐

2022-04-20 20:28:40

HDF 驱动框架鸿蒙操作系统

2022-05-03 22:25:57

Python浏览器语言

2022-05-10 16:04:40

编程语言PythonC语言

2022-05-07 10:09:01

开发Java日志

2022-03-08 11:17:54

函数指针回调函数C语言

2022-05-03 23:44:21

Python动态链接库Ctypes

2022-04-01 15:18:04

HarmonyHDF 驱动鸿蒙

2022-04-07 15:28:16

HarmonyOS鸿蒙操作系统

2022-04-12 11:38:06

C语言全局变量

2022-04-27 09:48:56

JS前端开发

2022-05-15 22:21:33

WiFi热点无效WiFiWindows 11

2022-04-22 18:48:46

LinuxLinux 内核C 语言

2022-04-30 09:09:55

SecureFXSecureCRT

2022-05-17 09:02:30

2022-05-13 09:34:00

Slik-wrang机器学习人工智能

2022-05-09 07:08:14

LinuxBashShell

2022-05-16 08:33:54

漏洞微软安全补丁

2022-03-16 10:14:55

C语言C++

2022-03-30 13:56:05

前端监控搭建

2022-04-21 09:26:41

FastDFS开源分布式文件系统

同话题下的热门内容

Python 字符串总结,建议收藏!这份Java日志格式规范,拿走不谢!Mybatis-Plus官方发布分库分表神器,一个依赖轻松搞定!后端思维篇:如何应用设计模式优化代码改变 Python 对象规则的黑魔法 Metaclass几种限流算法的Go语言实现JMeter关联之正则表达式提取器在 Go 中实现一个支持并发的 TCP 服务端

编辑推荐

使用Kotlin做开发一个月后的感想面试官问你什么是消息队列?把这篇甩给他!五大自动化测试的Python框架图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS)2018年最流行的十大编程语言,其中包括你用的语言吗?
我收藏的内容
点赞
收藏

51CTO技术栈公众号