2010-02-09 55 views
3

我经常使用的便利函数返回指向静态缓冲区是这样的:在地方作为参数传递给其他功能不便静态变量

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)); 

以上显然不会做我想做的,因为最后两个参数将指向相同的内存空间。我希望能够让上述工作正常工作,而不会有很多麻烦。

所以我的问题是:

是否有某种神奇的办法,我已经错过了完成我想要的东西没有做繁琐的配置&释放?

***** UPDATE 2010-04-20 *****

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

我想这会工作,但它也接壤矫枉过正。意见?

+1

在C领域内,没有什么好的答案。在某些情况下,您可以通过要求调用者通过缓冲区来解决问题,但这会失去很多便利。 – 2010-02-09 18:16:57

+2

这不仅仅是线程不安全,你会得到意想不到的副作用。假设你连续调用两次函数,将结果分配给两个不同的指针。当你输出两个字符串时,你会得到第二个值两次,因为两个指针指向同一个缓冲区。 – 2010-02-09 18:55:59

回答

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")); 

所以缓冲区是在栈上,而无需任何释放。它甚至是线程安全的。

+0

我已经无耻地把这个正确的答案,直到有人来到这里,并挑战它:) – joveha 2010-05-28 13:05:58

+0

TBH,这是一个很奇怪的做法。我会跳过INIT宏,然后直接调用MACRO两次,每次都在自己的行上。就像这样:'char * s1 = MACRO(“hello”); char * s2 = MACRO(“world”); some_func(s1,s2);'然后,我会在没有宏的情况下做到这一点。 – 2011-08-09 00:29:14

2

让调用者提供缓冲区(以及缓冲区的大小)。它是线程安全的,并且缓冲区通常可以放在堆栈中,所以不会导致堆分配的开销。

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函数实际上是这样工作的:您可以用空缓冲区指针和零缓冲区大小调用它,只是为了确定缓冲区大小进行虚拟运行)。

0

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

2

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

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

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


实现这一切似乎是

  • 了很多工作,(为了方便逻辑),也
  • 一个潜在的几个bug /问题

但是,只要

  • (s)为(被)足够大的缓冲器,并且
  • 使用这些方便的方法理解“的游戏规则”的逻辑,

此图案用品简单化的自动化堆管理系统,这是一个很好的东西在C(它不像Java,.NET和其他系统不提供内置的基于GC的堆管理)

+0

这听起来很合适,如果数据必须通过几个接口返回和回调持续存在,并且开销非常重要且需要设置,例如实时驱动程序。但是,调用者在堆栈上分配缓冲区有什么优势?这只是要求缓冲区溢出。 – Potatoswatter 2010-02-09 18:54:35

+0

有趣的方法。它会提供所寻求的便利性,其代价是实施这个以及它的继承危险(希望只有一次);) – joveha 2010-02-09 18:56:22

0

如果参数助手总是字面值在这个例子中),你可以使用一个宏:

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

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

预处理器创建一个从宏参数NUMLIT的字符串,并追加到“号码”,以建立一个单一的字符串文字,正如

"this" " " "string" 

被发射作为一个单一的字符串中,“这串”。