指向静态变量的不便之处

时间:2010-02-09 18:09:00

标签: c pointers memory-management

我经常使用便捷函数返回指向静态缓冲区的指针,如下所示:

char* p(int x) {
    static char res[512];

    snprintf(res, sizeof(res)-1, "number is %d", x));

    return res;
}

并将它们作为其他函数的参数使用:

...
some_func( somearg, p(6) );
....

然而,这种“便利性”除了不是线程安全之外还有一个恼人的缺点(可能还有更多原因):

some_func( somearg, p(6), p(7) );

上面显然没有做我想要的,因为最后两个参数将指向相同的内存空间。我希望能够在没有太多麻烦的情况下正常工作。

所以我的问题是:

我是否有一些神奇的方法让我无法完成我想要的事情,而无需进行繁琐的分配和释放吗

*****更新2010-04-20 *****

无耻的插件:看看我自己的答案here

我想它会起作用,但它也接近于矫枉过正。意见?

6 个答案:

答案 0 :(得分:8)

嗯,一种广泛使用的方法是在调用者上为结果准备内存缓冲区。呼叫者可以选择最喜欢的方法。

在您的情况下,将p写为

char* p(char *buffer, size_t max_length, int x) { 
  snprintf(buffer, max_length, "number is %d", x); 
  return buffer; 
} 

并将其命名为

char buffer1[512], buffer2[512];   
some_func( somearg, p(buffer1, sizeof buffer1 - 1, 6), p(buffer2, sizeof buffer2 - 1, 7) );   

这种方法至少有一个明显的缺点:一般情况下,调用者事先并不知道需要为缓冲区分配多少个字符。如果一个好的常量编译时值可用,则很容易,但在更复杂的情况下需要额外的工作,比如提供某种“预计算”功能,将所需的缓冲区大小作为运行时值返回。 (snprintf函数实际上是这样工作的:您可以使用空缓冲区指针和零缓冲区大小调用它,只是为了确定缓冲区大小而进行虚构运行。

答案 1 :(得分:2)

让调用者提供缓冲区(以及缓冲区的大小)。它是线程安全的,缓冲区通常可以放在堆栈上,因此不会带来堆分配的开销。

答案 2 :(得分:2)

只要您理解这是线程不安全的,并且您的逻辑期望“方便”方法返回的值仅在少数调用[[]的持续时间内有效。可能是各种各样的方法,你可以用两种不相关和可能互补的方式来扩展这种便利。

  • 为方便方法添加一个额外的参数,以便这些方法可以传递-optionally-返回值的容器(如果不能隐式设置这样的大小,也可以添加size_of_passed_buffer参数)。每当调用者提供缓冲区时,请使用它,否则,请使用静态缓冲区 顺便说一句,如果传递给方便方法的缓冲区是局部变量,它们的分配将自动(并且充分)地管理,遵循调用便捷方法的子程序的生命周期。

  • 使用循环缓冲区,在重用缓冲区元素之前允许给定数量的调用 这样的缓冲区也可以是全局的,即与多个“方便”方法共享(当然也需要a)在线程方面运行良好,b)共享指向缓冲区中下一个可用字节/元素的指针。


实现所有这些似乎是

  • 很多工作(为了方便逻辑),还有
  • 可能存在多个错误/问题

但是,只要

  • 缓冲区的大小适当,
  • 使用这些便利方法的逻辑理解“游戏规则”,

这种模式提供了一个简单的自动堆管理系统,这在C中是一件好事(与Java不同,.NET和其他系统不提供内置的基于GC的堆管理)

答案 3 :(得分:0)

简而言之,没有。 C不提供任何形式的自动堆管理,因此您可以自己跟踪已分配的内存。标准的类C解决方案是让调用者提供缓冲区,而不是在内部分配缓冲区。虽然这只是追踪内存的责任,但它往往会在一个更方便的地方结束。我想,如果你想在C中获得一种垃圾收集形式,你可以查看Boehm's conservative garbage collector

答案 4 :(得分:0)

如果助手的参数始终是文字(如示例所示),则可以使用宏:

#define P(NUMLIT) ("number is " #NUMLIT)

...
somefunc(somearg, P(6), P(7));
...

预处理器从宏参数NUMLIT创建一个字符串,并将其附加到“number is”以创建单个字符串文字,就像

一样
"this" " " "string" 

作为单个字符串文字发出,“this string”。

答案 5 :(得分:0)

我找到了另一种方法。像这样:

#define INIT(n) \
 int xi = 0; \
 char *x[n]; \

#define MACRO(s) \
 (++xi, xi %= sizeof(x)/sizeof(*x), x[xi] = alloca(strlen(s)+1), strcpy(x[xi], (s)), x[xi])

我可以这样打电话:

INIT(2);
some_func( somearg, MACRO("testing1"), MACRO("testing2"));

因此缓冲区在堆栈中而不需要任何释放。它甚至是线程安全的。