在c和c++中,自动变量在堆栈中分配内存。当包含自动变量的函数或代码块退出时,它们所占用的内存便被回收,它们的内容肯定会被下一个所调用的函数覆盖。这一切取决于堆栈中先前的自动变量位于何处,活动函数声明了什么变量,写入了什么内容等。原先自动变量地址的内容可能被立即覆盖,也可能稍后才被覆盖。
在c和c++中,数组作为参数传入函数或作为结果从函数中返回时,都会隐式使用到指针,即实际传递的是该数组首个元素的地址。因此当函数返回一个数组的时候,实际返回的是指向数组元素的指针,并不是一个真正的数组。
因此,当函数将执行结果存储在数组当中返回相应的指针的时候,需要确保这个数组不是一个在该函数的栈上分配的局部变量。不然的话,在函数执行结束以后,该数组的内存空间可能被重写,从而使函数的返回结果变得无效。
避免这样的问题的一个方法是将数组声明为static类型,下面的例子就用到了这种方法,这个函数用于将Internet地址转换为以圆点分割的十进制形式。
char *inet_ntoa(ad)
struct in_addr ad;
{
unsigned long int s_ad;
int a, b, c, d;
static char addr[20];
s_ad = ad.s_addr;
d = s_ad % 256;
s_ad /= 256;
c = s_ad % 256;
s_ad /= 256;
b = s_ad % 256;
a = s_ad / 256;
sprintf(addr, "%d.%d.%d.%d", a, b, c, d);
return addr;
}
该函数将执行结果保存在名为addr的缓冲区中,如果这个缓冲区没有被声明为static,则在函数返回以后,缓冲区的内容将会无效,返回的addr也就没有实际的作用了。
当然除了使用上面介绍的将数组的类型指定为static,还有其他的方法,可以参考这一篇文章。
下面我们将要从可重入性上来讨论上述几种方法。
使用全局变量或静态变量的函数在大多数情况下是不可重入的。也就是说,如果这个函数的一个实例在运行,程序中的其他线程就不能够调用这个函数。更为糟糕的是,在这个例子中,当该函数被再次调用前,函数的执行结果必须要保存到其他地方,否则,这个结果将会被新的结果重写,例如,在下面的代码中,使用到nadd_ntoa函数的地方并不能使用上面的inet_ntoa函数来替换。
(void)fprintf(ftrace, "%s Router Ad"
" from %s to %s via %s life=%d\n",
act, naddr_ntoa(from), naddr_ntoa(to),
ifp ? ifp->int_name : "?",
ntohs(p->ad.icmp_ad_life));
为了避免前面提到的问题,naddr_ntos函数对inet_ntoa函数进行了包装,将其执行结果保存在由四个临时缓冲区组成的循环列表当中。
/* convert IP address to a string, but not into a single buffer
*/
char *
naddr_ntoa(naddr a)
{
#define NUM_BUFS 4
static int bufno;
static struct {
char str[16]; /* xxx.xxx.xxx.xxx\0 */
} bufs[NUM_BUFS];
char *s;
struct in_addr addr;
addr.s_addr = a;
s = strcpy(bufs[bufno].str, inet_ntoa(addr));
bufno = (bufno+1) % NUM_BUFS;
return s;
#undef NUM_BUFS
}