C语言陷阱与技巧第39节,不用数据类型也能定义变量,拷贝函数参数是很有必要的

“拷贝”的重要性

在C语言程序开发中,函数的参数有时候需要拷贝一份才能安全的执行功能,下面是一个经常在面试题中出现的典型例子,请看:

double pow2(double *x)
{
    return (*x)*(*x);
}

上面这段C语言代码很简单,无非就是定义了 pow2() 函数接收参数 x,并计算其指向的值的平方值。但是读者应该明白的是,这样的写法是不安全的。上述C语言代码在某种程度上等价于下面这种写法,请看:

double pow2(double *x)
{
    double a = *x;
    double b = *x;
    return a*b;
}


从等价代码(或者汇编代码)可以看出,C语言函数 pow2 读取 x 指向的值实际上是分为两步的,所以下面这种情况就有可能发生:

x 指向的值为 3.14,程序员调用 pow2() 函数,原本应该得到 3.14 * 3.14 的结果。不过,x 指向的值随时可能改变,也就是说可能 a = 3.14,但是在执行 b = * x 之前,x 指向的值改变了,这就会导致 b 不等于 3.14,最终得到奇怪的结果。

这种“奇怪的结果”很难被发现,被称为“幽灵般”的 bug。

所以,实现 pow2() 函数时,更安全的做法是拷贝一份 x 指向的值,相关C语言代码如下,请看:

double pow2(double *x)
{
    // return (*x)*(*x);  // 不安全
    double a = *x;
    return a*a;
}

虽然多了一行“拷贝代码”,但是 pow2() 的功能却安全和稳定许多。事实上,在 Linux 操作系统内核源码中,有相当多的这种“拷贝代码”。

“拷贝代码”的麻烦之处

从上例可以看出,C语言函数在运行之前,先拷贝参数还是很有必要的。不过应该明白,要拷贝和使用C语言函数的参数,需要实现知道其数据类型,例如 pow2() 函数关心的参数是 double 型,所以用于拷贝参数 x 指向值的变量 a 是 double 型的。

如果 pow2() 函数的参数数据类型被修改了,那函数内部拷贝参数的变量数据类型也需要做同步修改,这样就略显繁琐了。一个小技巧是使用 typeof 关键字,请看下面的C语言代码示例:

double pow2(double *x)
{
    typeof(*x) a = *x;
    return a*a;
}

顾名思义,typeof() 可以将传递给它的变量的数据类型取出,并且以该数据类型定义变量。上面的C语言代码中,因为 * x 是 double 型的,所以:

typeof(*x) a;
// 等价于 
double a;

这样一来,即使后来 pow2() 的参数数据类型被修改,函数内部的逻辑也无需再做改动了,因为 typeof() 可以自动的取初修改后的数据类型。

typeof 的妙用

事实上,typeof 在C语言程序开发中相当好用,Linux 操作系统内核中有大量使用 typeof 的地方。typeof 常常和 ({...}) 符号联合使用,例如:

#define max(a,b) \
  ({ typeof (a) _a = (a); \
      typeof (b) _b = (b); \
    _a > _b ? _a : _b; })

上面这段C语言代码定义了一个安全求最大值的宏,typeof 可以自动的取出传入变量的数据类型,因此该宏可以求任意数据类型的最大值。

按照我们前面的讨论,使用函数式宏定义,可能会产生多次“副作用”引起的不预期结果,而这里的 max 宏显然可以避免这种情况出现。

想想为什么?

关于 typeof 关键字,下面是一些比较常用的使用实例,相关C语言代码如下,请看:

定义一个 x 指向的变量 y:

typeof(*x) y;

定义一个 x 指向的变量数组:

typeof(*x) y[4];

定义一个 char 型的指针数组 y:

typeof(typeof(char *)[4]) y;

它和下面这行C语言代码是等价的:

char *y[4];

如果读者理解了上面几条实例,写出下面这样看着更加直白的宏就简单了:

#define pointer(T)  typeof(T *)
#define array(T, N) typeof(T [N])

现在我们再定义 char 型的指针数组就更加清晰明了了:

array (pointer (char), 4) y;

array 说明 y 是一个数组,它的类型是 char pointer(指针)型的,一共有 4 个元素。

小结

本节先讨论了在C语言函数中,拷贝值可能会改变的参数的重要性,并在此基础上讨论了C语言新特性关键字 typeof,可以看出,合理的使用它,我们能够写出更加清晰易懂的C语言代码,这对于后期维护,以及分享给他人,都是大有裨益的。

阅读更多:   杂谈 , C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif