We have this function prototype:
BNode *b_new_node(const char *name, int pos, int len, const char *val);
Most of the code using this(and similar) are autogenerated code, and looks like:
b = b_new_node("foo.bar.id.1", 0, 10, some_data);
The function allocates a new BNode and copies the val
string into it, but it just assigns the name
member to a pointer, e.g.
b_strlcpy(new_node->val, val, sizeof new_node->val);
new_node->name = name;
This wrecks havoc if the first argument in b_new_node("foo.bar.id.1", 0, 10, some_data); is not a string literal, or otherwise something with static storage duration, but e.g. a buffer on the stack.
Is there anyway, with gcc (other compilers are of interest too), we can have a compile time check that this argument is passed in is of static storage ?
(ofcourse the easy way to avoid these possible problems is to copy that argument too into the node - the measurements we did with that approach rises the memory need by 50% and slows the program down by 10%, so that approach is undesirable).
This will detect string literals:
#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
So you can wrap your b_new_node() function in a macro, perhaps just for a debug build, and harness the division by zero warnings.
Note that it only detects string literals as "arguments", not static storage, e.g.
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";
Generally no; there is no C-provided facility to know whether or not a pointer points to something in static storage. Specific environments and data structures may change the circumstances - such as checking if the pointed-to address is in a read-only memory segment.
Saving space by countering duplication
To eliminate duplicated values, as pointed to by name
, you could use the flyweight pattern which is not very different from string interning.
Basically you build a central set of encountered tokens and store only the reference to each token. The references could be array indices or pointers.
To be able to clean-up quickly, you could combine the flyweight pattern with reference counting where a count of zero, means no references left.
To keep performance of the central store high, use a data structure where look-up is fast such as a set, or with a map if reference counting is used.
You can make BNode
conditionally copy the name. That will require and extra bit of storage in 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);
}