4

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

  • 2
    Sorry not to answer a good question, but: Are you sure you need a *static* string? A string that lives longer than the node would suffice, I think? The classic way of solving this is documentation about the expected lifetime of `name`'s contents, and programmers reading the documentation. Suboptimal, I know, but the standard. – thiton Feb 12 '13 at 10:19
  • @thiton Sure, in principle you only need something that lives longer than the node. But since most of the code using this is auto generated, with a string literal, and the fact that detecting a string literal or static storage sounds like an easier job for a compiler than tracking arbitary object lifetime - that'll be a suitable approach. –  Feb 12 '13 at 10:26
  • @larsmans surely that question seems to be about runtime checking ? the OP seem to only need a compile time check. – nos Feb 12 '13 at 15:14

3 Answers3

1

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";
nos
  • 223,662
  • 58
  • 417
  • 506
1

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.

VilleWitt
  • 21
  • 5
0

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);
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • While a possibility, this requires the programmer to do the right thing, instead of having the compiler check that fact. It seems that __builtin_constant_p() could help me, but I don't have it quite working yet.. –  Feb 12 '13 at 14:42
  • @user964970 In this case `__builtin_constant_p` can only be used at the call site, i.e. again the programmer should do the right thing, or wrap it in a macro. – Maxim Egorushkin Feb 12 '13 at 14:46