Google C++编程风格指南(二)之函数的相关规范

1.内联函数的使用规范

定义:内联函数是指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。

特点:是编译器可能会将其内联展开,编译时,类似于宏替换,使用函数体替换调用处的函数名,以减少函数调用的开销,无需按通常的函数调用机制调用内联函数。

优点:当函数体比较小的时候,内联该函数可以令目标代码更加高效。

缺点:滥用内联将导致程序变慢,内联有可能使目标代码量增加或减,返取决于被内联的函数的大小。内联较短小的存取函数通常会减少代码量,但内联一个较大的函数(注:如果编译器允许的话)将显著增加代码量。在现代处理器上,由亍更好的利用指令缓存(instruction cache),小巧的代码往往执行更快。

使用inline函数应该遵循以下几点: (1)内联函数最好不要超过10行;

(2)对于析构函数应慎重对待,析构函数往往比其表面看起来要长,因为有一些隐式成员和基类析构函数(如果有的话)被调用;

(3)递归函数不应该被声明为内联函数。原因是递归调用堆栈的展开并不像循环那么简单,比如递归次数在编译时可能是未知的,大多数编译器都不支持内联递归函数。

考察如下代码,一个简单的递归调用:

#include <iostream>
using namespace std;

inline void print(int n){
    --n;
    if(n>0)
        print(n);
    else 
        for(int i=0;i<5;++i)
            cout<<"this is inline function:"<<i<<endl;

}

int main(){
    print(10);
}

在VS2012中转到反汇编,其汇编代码为:

int main(){
002E12B0  push        ebp  
002E12B1  mov         ebp,esp  
002E12B3  and         esp,0FFFFFFF8h  
    print(10);
002E12B6  call        print (02E1270h)  
}

可见,即使将递归函数print(int n)定义为inline函数,实际上编译器并未将其内联展开,按照正常的函数去调用它。

(4)虚函数不应该被申明为内联函数。因为虚函数的调用较普通函数复杂,需要运行时通过查找虚函数表动态获取虚函数的入口地址,编译器编译阶段是不能确定虚函数的入口地址,故不能将其在编译时静态展开。

(5)如果对析构函数内联,主要原因是在类体重定义,为了方便抑或是其他原因,应对其行为给出文档说明。

2.函数相关规范

2.1函数参数顺序(Function Parameter Ordering)

定义函数时,参数顺序为:输入参数在前,输出参数在后。

C/C++函数参数分为输入参数和输出参数两种,有时输入参数也会输出(注:值被修改时)。输入参数一般传值或常数引用(const references),输出参数或输入/输出参数为非常数指针(non-const pointers)。对参数排序时,将所有输入参数置于输出参数之前。不要仅仅因为新添加的参数,就将其置于最后,而应该依然置于输出参数之前。

注意,返一点并不是必须遵循的规则,输入/输出两用参数(通常是类/结极体发量)混在其中,会使得规则难以遵循。

2.2不要设计多用途面面俱到的函数

多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。 应编写功能单一集中的函数。

2.3函数的规模

函数的规模尽量限制在80行以内 ,不包括注释和空格行。其次,避免设计多参数函数,不使用的参数从接口中去掉,其目的是为了减小函数接口的复杂度。

2.4尽量编写线程安全函数与可重入函数

2.4.1什么是线程安全函数

线程安全函数是多线程情况下,可安全的被多个线程并发执行的函数。

确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问时,如果要保证线程安全,则必须通过加锁的方式。

线程安全函数与线程不安全函数示例:

static int tmp;  
//线程不安全函数
void func1(int* x, int* y) {  
     tmp=*x;  
     *x=*y;  
     *y=tmp;  
} 

//线程安全函数
void func2(int* x, int* y) {  
     int tmp;  
     tmp=*x;  
     *x=*y;  
     *y=tmp;  

}  

func1是线程不安全函数,因为func1在被多线程并发调用时,使用的共享变量tmp可能被其它线程的func1改变,从而导致函数结果的不确定性。解决办法就是给全局变量tmp加锁,或者使用私有局部变量,函数func2就这这样做的。

2.4.2什么是可重入函数

可重入函数的定义本人目前没有找到比较权威的参考资料,个人理解是:可以被中断处理函数调用的线程安全函数是可重入函数。关于可重入的概念,可以参考可重入.维基百科

也就是说,可重入函数必定可以被安全的并发执行。安全指函数的运行结果必须满足预期,不存在不确定性。

对于百度百科的描述,实际上是介绍了一个特殊的场景下,满足这个场景的线程安全函数就是可重入函数。这个特殊的场景就是函数在响应中断期间,被中断处理函数再次调用,这就是“重入”,重新进入的形象描述。再次被调用可以安全的进行,这就是“可重入”。相反, 不可重入(non-reentrant)不可重入的后果主要体现在象终端处理函数需要重入的情况中,如果信号处理函数中使用了不可重入的函数,则可能导致程序的错误甚至崩溃。

要确保函数可重入,需满足以下几个条件: (1)不在函数内部使用静态或全局数据; (2)不返回静态或全局数据,所有数据都由函数的调用者提供; (3)使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据; (4)不调用不可重入函数。

2.4.3可重入函数与线程安全函数的区别

(1)关系 可重入函数是线程安全函数的子集,即可重入函数一定是线程安全函数,线程安全函数不一定是可重入函数。

关系图如下:

(2)区别 在线程安全函数可以对共享地址空间数据加锁,可重入函数则不能。因为在可重入函数响应中断时,中断处理函数若再次调用该函数时,会发生死锁。

其它的区别,本人暂未发现,理解到,若有人知晓,希望勿吝赐教,留言告知。

在多线程条件下,应当做到函数是线程安全的,更进一步,做到可重入 。


参考文献

[1]百度百科.可重入函数 [2]百度文库.Google C++编码规范中文版

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员互动联盟

【基础编程】聊聊C语言-变量的寿命

上一篇在编程世界的容器中,我们讲述了程序中的数据都存储在变量中,而变量根据数据类型的不同所占用的内存大小也不一样。但是计算机的内存大小是有限的不可能无限的分配下...

3887
来自专栏noteless

[三]java8 函数式编程Stream 概念深入理解 Stream 运行原理 Stream设计思路

        流不是存储元素的数据结构;相反,它通过一个计算操作的管道,从一个数据源,如数据结构、数组、生成器函数或i/o通道中传递元素

4065
来自专栏北京马哥教育

Python程序员最常犯的十个错误,看完你自己都笑了

本文由马哥教育Python自动化实战班4期学员推荐,转载自简书,作者为EarlGrey,内容略经小编改编和加工,观点跟作者无关,最后感谢作者的辛苦贡献与付出。 ...

2874
来自专栏xingoo, 一个梦想做发明家的程序员

const指南

基本词义  意思就就是说利用const进行修饰的变量的值在程序的任意位置将不能再被修改,就如同常数一样使用!  使用方法 const int a=1;//这里定...

20210
来自专栏C语言及其他语言

Python程序员最常犯的十个错误

来源:编程派 ? 不管是在学习还是工作过程中,人都会犯错。虽然Python的语法简单、灵活,但也一样存在一些不小的坑,一不小心,初学者和资深Python程序...

3287
来自专栏微信公众号:Java团长

Java异常处理和设计

在程序设计中,进行异常处理是非常关键和重要的一部分。一个程序的异常处理框架的好坏直接影响到整个项目的代码质量以及后期维护成本和难度。试想一下,如果一个项目从头到...

1463
来自专栏CDA数据分析师

Python | 十个Python程序员易犯的错误

不管是在学习还是工作过程中,人都会犯错。虽然Python的语法简单、灵活,但也一样存在一些不小的坑,一不小心,初学者和资深Python程序员都有可能会栽跟头。本...

28410
来自专栏前端说吧

【消灭代办】第一周 - 敏感词判断

  一堆字符串组成的数组,给你一个字符串,让你去查找这个字符串是否在这个数组当中?

1081
来自专栏菩提树下的杨过

ruby学习笔记(11)--symbol与hash参数

symbol是啥就不深入的讨论了,只简单说说symbol的好处 ruby内部对于每个对象,都会有一个数字id用来标识并区分,可以用xxx.object_id来查...

18610
来自专栏Java面试笔试题

内存中的栈(stack)、堆(heap)和静态区(static area)的用法

通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间;而通过new关键字和构造器创建的对象放在堆空间;程序中的字面...

1196

扫码关注云+社区

领取腾讯云代金券