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

时间:2015-03-06 10:14:30

标签: c

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

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

我想象那样的东西。

我的例子:

场景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

场景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

用户如何调用mylib_create_element(b);不受我的控制。如果他在mylib_destroy_element之前解放,它就会崩溃。所以必须清理mylib_destroy_element

8 个答案:

答案 0 :(得分:27)

我最近遇到了类似的情况。这是我做的:

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

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

答案 1 :(得分: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));
}

答案 2 :(得分:12)

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

然而,正如@ Mints97在他的回答中提到的,基本上这种方法在架构上是不正确的:你强迫你的库的用户做一些明确的操作,如果他忘记了,它最有可能导致内存泄漏(甚至是应用程序崩溃)。所以仅在

时使用它
  1. 您希望大幅减少分配金额。在我的例子中,它是JSON节点名称,在程序的生命周期内永远不会改变。
  2. 您可以很好地控制图书馆消费者的代码。就我而言,库附带了二进制文件并与它们紧密绑定。
  3. 实施例

    #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);
    

    测试性能(注释#1)

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

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

    我的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 */
      

    (不完全确定Sparse的输出)

    • 使用简单的typedef使编译器在您执行错误时发出警告:

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

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

    可在此处找到包含更改的完整来源。如果使用-DAAS_STRICT进行编译,则会产生大量错误:https://ideone.com/xxmSat即使是正确的代码,它也会抱怨strcpy()(不会在ideone上重现)。

答案 3 :(得分:6)

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

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

最好以一种不会遇到此问题的方式修改代码。

答案 4 :(得分:5)

这是一种实用的方法:

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

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

因此,在您要取消分配该字符串的位置,您只需将其地址与文字字符串"dont free me!"的地址进行比较即可:

if (b != "dont free me!") // address comparison
    free(b);

再次强调这一点,它不是由C语言标准强加的,但它实际上是由任何体面的语言编译器实现的。


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

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

答案 5 :(得分:4)

您可以执行以下操作:

  typedef struct 
{
 int is_literal;
 char * array;
} elem;

每次在堆上分配elem.array时,只需将is_literal设置为0.当您将数组设置为literal时,将该标志设置为非零值,例如:

elem foo;
foo.array = "literal";
foo.is_literal = 1 ;

elem bar;
bar.array = (char*) (malloc(sizeof(char) * 10)) ;
bar.is_literal = 0;

然后在客户端:

if(!bar.is_literal) {
free(bar.array);
}

这很简单。

答案 6 :(得分:4)

这正是规则的原因,只有创建字符串的代码或模块可以释放它。换句话说,每个字符串或数据片段都由创建它的代码单元“拥有”。只有主人可以释放它。函数永远不应该释放它作为参数接收的数据结构。

答案 7 :(得分:2)

早在早期80386最多可以有8兆字节的RAM,并且在其他杂志文章中解释了制作对象的想法,我不喜欢复制完美的文字进入字符串对象(分配和释放内部副本),我向Bjarne询问了这一点,因为粗略的字符串类是他的C ++奇特的例子之一。

他说不要担心。

这与文字与其他char*指针有什么关系?您可以始终拥有内存。从你寻找不同记忆段的想法中思考。

或者更普遍的是,可能会或可能不会给出所有权,没有办法告诉,并且需要存储一个标志:&#34;嘿,这是一个堆对象,但其他人仍在使用它以后会好好处理好吗?&#34;

对于容易处理的情况,它在堆上#34;或者&#34;不是&#34; (文字,全局,基于堆栈),你可以知道free函数。如果您提供了一组匹配的allocate / maybe-free,则可以将其写入知道其控制的内存。