如何区分堆或文字中的字符串?

alk*_*ows 28 c

我有一个用例,我可以获得在内存或文字中分配的字符串指针.现在后者不能被释放,所以如果我通过错误的话就会出问题.有没有办法知道哪一个被分配哪个不分配?

char *b = "dont free me!";
if(!IS_LITERAL(b)) {
    free(b);
}
Run Code Online (Sandbox Code Playgroud)

我想象那样的事情.

我的例子:

场景1:字面意思

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
Run Code Online (Sandbox Code Playgroud)

场景2:在堆中

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
Run Code Online (Sandbox Code Playgroud)

用户如何调用mylib_create_element(b);不受我的控制.如果他在mylib_destroy_element崩溃之前释放.所以它必须mylib_destroy_element清理干净.

Min*_*s97 27

我最近有类似的案子.这是我做的:

如果您正在创建一个接受字符串指针然后使用它来创建对象(mylib_create_element)的API,那么最好将字符串复制到单独的堆缓冲区,然后由您自行决定是否释放它.这样,用户负责释放他在API调用中使用的字符串,这是有道理的.毕竟,这是他的字符串.

请注意,如果您的API依赖于用户在创建对象后更改字符串,则无法使用此功能!


Mar*_*ian 23

在大多数Unix上,都有值'etext'和'edata'.如果你的指针在'etext'和'edata'之间,那么它应该是静态初始化的.任何标准都没有提到这些值,因此使用不可移植,风险自负.

例:

#include<stdio.h>
#include<stdlib.h>

extern char edata;
extern char etext;

#define IS_LITERAL(b) ((b) >= &etext && (b) < &edata)

int main() {
    char *p1 = "static";
    char *p2 = malloc(10);
    printf("%d, %d\n", IS_LITERAL(p1), IS_LITERAL(p2));
}
Run Code Online (Sandbox Code Playgroud)


mya*_*aut 12

您只能要求用户将其输入明确标记为文字或分配字符串.

但是,正如@ Mints97在他的回答中提到的那样,基本上这种方法在架构上是不正确的:你强迫你的库的用户做一些明确的操作,如果他忘记了,它最有可能导致内存泄漏(甚至是应用程序崩溃) .因此,只有在以下情况下使

  1. 您希望大幅减少分配数量.在我的例子中,它是JSON节点名称,在程序的生命周期内永远不会改变.
  2. 您可以很好地控制库中消费者的代码.在我的例子中,库随附二进制文件并与它们紧密绑定.

实施例

#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);
Run Code Online (Sandbox Code Playgroud)

测试性能(注1)

我使用以下代码(800k迭代)对我的libtsjson库进行了基准测试:

    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);
Run Code Online (Sandbox Code Playgroud)

我的CPU是Intel Core i7 860.如果NODE_NAME只是一个宏,每次迭代的时间是479ns. 如果NODE_NAME是内存分配,每次迭代的时间是609ns

提示用户或编译器(注释#2)

  • 为所有这样的指针添加提示,即Linux静态源分析器Sparse可能会捕获这样的问题

    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 */
    
    Run Code Online (Sandbox Code Playgroud)

(不完全确定稀疏的输出)

  • 使用simple typedef可以使编译器在您执行错误时发出警告:

    #ifdef AAS_STRICT
    typedef struct { char a; } *aas_t;
    #else
    typedef char *aas_t;
    #endif
    
    Run Code Online (Sandbox Code Playgroud)

这种方法又是一个肮脏的C黑客世界的又一步,即sizeof(*aas_t)现在> 1.

可以在此处找到完整的变更来源.如果使用-DAAS_STRICT它编译将产生大量错误:https:strcpy()//ideone.com/xxmSat 即使是正确的代码,它也可以抱怨(不在ideone上复制).

  • 这有效,但它的API设计很糟糕.C中的所有其他函数只是简单的字符串,为什么不是你的?当你看到像``mylib_create_elem(char*)``这样的函数签名时,这并不是你所期望的!如果你错误地使用它,你将默默地泄漏内存而不会得到任何警告.Mints97的答案好多了...... (3认同)

Arj*_*ran 6

简单的答案是你不能这样做,因为C语言没有划分堆栈,堆和数据部分.

如果你想猜测 - 你可以收集堆栈上第一个变量的地址,调用函数的地址和分配给堆的内存字节的地址; 然后将它与你的指针进行比较 - 一个非常糟糕的做法,没有保证.

您最好以不会遇到此问题的方式修改代码.

  • 有趣的事实:单词*stack*在C99的文本中没有出现.(我没有检查过C11;这个*可能随着线程的引入而改变了.)需要支持递归函数调用,但是传统的堆栈,甚至是某种类型的LIFO数据结构都不是必需的.符合要求; 比如看看"MTA上的切尼" (2认同)

bar*_*nos 5

这是一个实用的方法:

虽然C语言标准没有规定这一点,但是对于代码中给定文字字符串的所有相同事件,编译器会在可执行映像的RO数据部分中生成单个副本.

换句话说,代码中每次出现的文字字符串"dont free me!"都会转换为相同的内存地址.

因此,在您要释放该字符串的位置,您可以简单地将其地址与文字字符串的地址进行比较"dont free me!":

if (b != "dont free me!") // address comparison
    free(b);
Run Code Online (Sandbox Code Playgroud)

为了再次强调这一点,它不是由C语言标准强加的,而是由该语言的任何体面的编译器实际实现.


以上只是一个直接指向手头问题的实用技巧(而不是这个问题背后的动机).

严格地说,如果你已经达到了实现中你必须区分静态分配的字符串和动态分配的字符串的点,那么我倾向于猜测你的初始设计在某个地方是有缺陷的.