How to distinguish between strings in heap or lite

2020-08-09 07:53发布

I have a use case where I can get pointers of strings allocated either in memory or literals. Now the latter can't be freed so that's a problem if I pass the wrong one. Is there a way to know which one is allocated and which not?

char *b = "dont free me!";
if(!IS_LITERAL(b)) {
    free(b);
}

I imagine something like that.

My example:

Scenario 1: literal

char *b = "dont free me!";
scruct elem* my_element = mylib_create_element(b);
// do smth
int result = mylib_destroy_element(my_element); // free literal, very bad

Scenario 2: in heap

char *b = malloc(sizeof(char)*17); // example
strncpy(b, "you can free me!",17);

scruct elem* my_element = mylib_create_element(b);
// do smth
int result = mylib_destroy_element(my_element); // free heap, nice

How the user calls mylib_create_element(b); is not under my control. If he frees before mylib_destroy_element it can crash. So it has got to be mylib_destroy_element that cleans up.

标签: c
8条回答
冷血范
2楼-- · 2020-08-09 08:47

You can only ask user to explicitly mark their input as literal or allocated string.

However, as @Mints97 mentions in his answer, basically this approach is architecturally incorrect: you force user of your library for some explicit actions, and if he forgets to, it leads most likely to a memory leak (or even to an application crash). So use it only if:

  1. You want to drastically reduce amount of allocations. In my case it was JSON node names, that never change during lifetime of a program.
  2. You have good control of code of consumers of your library. In my case, libraries are shipped with binaries and tightly bound to them.

Implementation example

#define AAS_DYNAMIC             'D'
#define AAS_STATIC              'S'

#define AAS_STATIC_PREFIX       "S"
#define AAS_CONST_STR(str)      ((AAS_STATIC_PREFIX str) + 1)

char* aas_allocate(size_t count) {
    char* buffer = malloc(count + 2);
    if(buffer == NULL)
        return NULL;

    *buffer = AAS_DYNAMIC;

    return buffer + 1;
}

void aas_free(char* aas) {
    if(aas != NULL) {
        if(*(aas - 1) == AAS_DYNAMIC) {
            free(aas - 1);
        }
    }
}

...

char* s1 = AAS_CONST_STR("test1");
char* s2 = aas_allocate(10);

strcpy(s2, "test2");

aas_free(s1);
aas_free(s2);

Testing performance (note #1)

I benchmarked my libtsjson library with following code (800k iterations):

    node = json_new_node(NULL);
    json_add_integer(node, NODE_NAME("i"), 10);
    json_add_string(node, NODE_NAME("s1"), json_str_create("test1"));
    json_add_string(node, NODE_NAME("s2"), json_str_create("test2"));
    json_node_destroy(node);

My CPU is Intel Core i7 860. If NODE_NAME is just a macro, time per iteration was 479ns If NODE_NAME is a memory allocation, time per iteration was 609ns

Hinting user or compiler (note #2)

  • Add a hint to all such pointers, i.e. Linux static source analyser Sparse may catch such issues

    char __autostring* s1 = aas_copy("test"); /* OK */
    char __autostring* s2 = strdup("test");   /* Should be fail? */
    char* s3 = s1;                            /* Confuses sparse */
    char* s4 = (char*) s1;                    /* Explicit conversion - OK */
    

(not completely sure about outputs of Sparse)

  • Use simple typedef to make compiler raise a warning when you do something wrong:

    #ifdef AAS_STRICT
    typedef struct { char a; } *aas_t;
    #else
    typedef char *aas_t;
    #endif
    

This approach is one more step to a world of a dirty C hacks, i.e. sizeof(*aas_t) is now > 1.

Full source with changes may be found here. If compiled with -DAAS_STRICT it will raise tons of errors: https://ideone.com/xxmSat Even for correct code it can complain about strcpy() (not reproduced on ideone).

查看更多
We Are One
3楼-- · 2020-08-09 08:47

The simple answer is you cannot do this since C language does not demarcate stack, heap and data section.

If you wanted to have a guess - you could collect address of the first variable on the stack, address of the calling function and address of a byte of memory allocated to heap; and then compare it with your pointer - a very bad practice with no guarantees.

It's best for you to revamp your code such a way that you don't come across this issue.

查看更多
登录 后发表回答