C/C++返回内部静态成员的陷阱
副标题#e#
配景
在我们用C/C++开拓的进程中,老是有一个问题会给我们带来苦恼。这个问题就是函数内和函数外代码需要通过一块内存来交互(好比,函数返回字符串),这个问题困扰和许多开拓人员。假如你的内存是在函数内栈上分派的,那么这个内存会跟着函数的返回而被弹栈释放,所以,你必然要返回一块函数外部尚有效的内存。
这是一个让无数人困扰的问题。假如你一不小心,你就很有大概在这个上面出错误。虽然今朝有许多办理要领,假如你熟悉一些尺度库的话,你可以看到很多各式百般的办理要领。概略来说有下面几种:
1)在函数内部通过malloc或new在堆上分派内存,然后把这块内存返回(因为在堆上分派的内存是全局可见的)。这样带来的问题就是潜在的内存问题。因为,假如返回出去的内存不释放,那么就是memory Leak。可能是被多次释放,从而造成措施的crash。这两个问题都相当的严重,所以这种设计要领并不推荐。(在一些Windows API中,当你挪用了一些API后,你必须也要挪用他的某些API来释放这块内存)
2)让用户传入一块他本身的内存地点,而在函数中把要返回的内存放到这块内存中。这是一个今朝普遍利用的方法。许多Windows API函数或是尺度C函数都需要你传入一个buffer和这个buffer的长度。这种方法对我们来说应该是多如牛毛了。这种方法的长处就是由函数外部的措施来维护这块内存,较量简显直观。但问题就是在利用上稍许有些贫苦。不外这种方法把出错误的机率减到了最低。
3)第三种方法显得较量另类,他操作了static的特性,static的栈内存一旦分派,那这块内存不会跟着函数的返回而释放,并且,它是全局可见的(只要你有这块内存的地点)。所以,有一些函数利用了static的这个特性,即不消利用堆上的内存,也不需要用户传入一个buffer和其长度。从而,利用得本身的函数长得很大度,也很容易利用。
#p#副标题#e#
这里,我想对第三个要领举办一些接头。利用static内存这个要领看似不错,可是它有让你想象不到的陷阱。让我们来用一个实际产生的案例来举一个例子吧。
示例
有过socket编程履历的人必然知道一个函数叫:inet_ntoa,这个函数主要的成果是把一个数字型的IP地点转成字符串,这个函数的界说是这样的(留意它的返回值):
char *inet_ntoa(struct in_addr in);
显然,这个函数不会分派堆上的内存,而他又没有让你传一下字符串的buffer进入,那么他必然利用“返回static char[]”这种要领。在我们继承我们的接头之前,让我们先相识一下IP地点相关的常识,下面是inet_ntoa这个函数需要传入的参数:(也许你会很奇怪,只有一个member的struct还要放在struct中干什么?这应该是为了措施日后的扩展性的思量)
struct in_addr {
unsigned long int s_addr;
}
对付IPV4来说,一个IP地点由四个8位的bit构成,其放在s_addr中,高位在后,这是为了利便网络传输。假如你获得的一个s_addr的整型值是:3776385196。那么,打开你的Windows计较器吧,看看它的二进制是什么?让我们从右到左,8位为一组(如下所示)。
11100001 00010111 00010000 10101100
再把每一组转成十进制,于是我们就获得:225 23 16 172, 于是IP地点就是 172.16.23.225。
好了,言归正传。我们有这样一个措施,想记录网络包的源地点和目地地点,于是,我们有如下的代码:
struct in_addr src, des;
........
........
fprintf(fp, "源IP地点<%s>\t 目标IP地点<%s>\n", inet_ntoa(src), inet_ntoa(des));
会产生什么样的功效呢?你会发明记录到文件中的源IP地点和目标IP地点完全一样。这是什么问题呢?于是你开始调试你的措施,你发明src.s_addr和des.s_addr基础纷歧样(如下所示)。可为什么输出到文件的源和目标都是一样的?莫非说是inet_ntoa的bug?
src.s_addr = 3776385196; //对应于172.16.23.225
des.s_addr = 1678184620; //对应于172.16.7.100
原因就是inet_ntoa()“自作智慧”地把内部的static char[]返回了,而我们的措施正是踩中了这个陷阱。让我们来阐明一下fprintf代码。在我们fprintf时,编译器先计较inet_ntoa(des),于是其返回一个字符串的地点,然后措施再去求inet_ntoa(src)表达式,又获得一个字符串的地点。这两个字符串的地点都是inet_ntoa()中谁人static char[],显然是同一个地点,而第二次求src的IP时,这个值的des的IP地点内容必将被src的IP包围。所以,这两个表达式的字符串内存都是一样的了,此时,措施会挪用fprintf把这两个字符串(其实是一个)输出到文件。所以,获得沟通的功效也就不奇怪。
仔细看一下inet_ntoa的man,我们可以看到这句话:The string is returned in a statically allocated buffer, which subsequent calls will overwrite. 证实了我们的阐明。
小结
#p#分页标题#e#
让我们各人都抚心自问一下,我们在写措施的进程傍边是否利用了这种要领?这是一个较量危险,容易堕落的要领。这种陷阱让人防不胜防。想想,假如你有这样的措施:
if ( strcmp( inet_ntoa(ip1), inet_ntoa(ip2) )==0 ) {
.... ....
}
本想判定一下两个IP地点是否一样,却不意掉入了谁人陷阱——让这个条件表达式永真。
这个工作汇报我们下面几个原理:
1)慎用这种方法的设计。返回函数内部的static内存有很大的陷阱。
2)假如必然要利用这种方法的话。你就必需严肃地汇报所有利用这个函数的人,千万不要在一个表达式中多次利用这个函数。并且,还要汇报他们,不copy函数返回的内存的内容,而只是生存返回的内存地点或是引用是没用的。否则的话,效果概不认真。
3)C/C++是很危险的世界,假如你不清楚他的话。照旧回火星去吧。
附:看过Efftive C++的伴侣必然知道个中有一个条款(item 23):不要试图返回工具的引用。这个条款中也对是否返回函数内部的static变量举办了接头。功效也是持否认立场的。