我们有这个函数原型:
BNode *b_new_node(const char *name, int pos, int len, const char *val);
使用此代码(和类似代码)的大多数代码都是自动生成的代码,如下所示:
b = b_new_node("foo.bar.id.1", 0, 10, some_data);
该函数分配一个新的BNode并将val
字符串复制到其中,但它只是将name
成员分配给指针,例如
b_strlcpy(new_node->val, val, sizeof new_node->val);
new_node->name = name;
如果b_new_node中的第一个参数(“foo.bar.id.1”,0,10,some_data),则会造成严重破坏;不是字符串文字,或者是具有静态存储持续时间的东西,但是例如堆栈上的缓冲区。
无论如何,对于gcc(其他编译器也很感兴趣),我们可以通过编译时检查传入此参数是静态存储吗?
(当然,避免这些可能问题的简单方法是将该参数复制到节点中 - 我们使用该方法进行的测量将内存需求提高50%并使程序减慢10%,因此该方法是不希望的)。
答案 0 :(得分:1)
这将检测字符串文字:
#include <stdio.h>
#define PRINT_IT(c) do {\
if (__builtin_constant_p(c))\
print_it(c, 1);\
else \
1/__builtin_constant_p(c);\
} while (0)
void print_it(const char *c, int is_static)
{
printf("%s is a constant %d\n", c, is_static);
}
int main(int argc, char *argv[])
{
char bar[] = "bar";
PRINT_IT("Foo"); //line 19
PRINT_IT(bar); //line 20
return 0;
}
$ gcc foo.c
foo.c: In function ‘main’:
foo.c:20: warning: division by zero
因此,您可以将b_new_node()函数包装在宏中,可能仅用于调试版本,并利用除零警告。
请注意,它只检测字符串文字为“参数”,而不是静态存储,例如
const char *foo = "foo";
PRINT_IT(foo); //will generate a warning
PRINT_IT("foo"); //will not generate a warning
PRINT_IT(global_foo); //will generate a warning, even when.
//global_foo is const char *foo = "foo"; or
//global_foo is const char foo[] = "foo";
答案 1 :(得分:1)
一般没有;没有C提供的工具来知道指针是否指向静态存储中的某些东西。特定环境和数据结构可能会改变情况 - 例如检查指向的地址是否在read-only memory segment中。
为了消除name
指向的重复值,您可以使用与the flyweight pattern没有什么不同的string interning。
基本上,您构建了一组中心遇到的令牌,并仅存储对每个令牌的引用。引用可以是数组索引或指针。
为了能够快速清理,你可以将flyweight模式与reference counting结合起来,其中计数为零,意味着没有任何参考。
要保持中央商店的性能较高,请使用快速查找的数据结构(如集合),或使用map如果使用reference counting。
答案 2 :(得分:0)
您可以BNode
有条件地复制名称。这将需要BNode
中的额外存储空间。 E.g:
typedef struct BNode {
char const* name;
unsigned char : own_name;
} BNode;
void b_copy_name(BNode* n) {
if(!n->own_name) {
char* p = strdup(n->name);
if(p) {
n->own_name = 1;
n->name = p;
}
else {
abort();
}
}
}
void b_destroy(BNode* n) {
// ...
if(n->own_name)
free(n->name);
}