如何在C中使用concat字符串指针时避免内存泄漏

时间:2017-12-11 20:08:52

标签: c string pointers memory-leaks concatenation

我正在为一个编程语言类的解析器工作,并且在将我的表达式树转换为预订排列中的字符串时遇到了内存泄漏问题。在我得到它之前,当在exprToString中为字符串分配内存时它是100 * sizeof(char),但是mem泄漏结果要大得多。所以,我摆脱了100 *但我仍然得到非ascii打印输出和奇怪的反复“retu”......

char* concat(const char *s1, const char *s2)
{
    char *result = malloc(strlen(s1)+strlen(s2)+1);//+1 for the null-
    terminator
    //free(result);
    //in real code you would check for errors in malloc here
    strcpy(result, s1);
    strcat(result, s2);
    return result;
}

char* exprToString(Expr* ex){
    char *string = (char*)malloc(sizeof(char));
    //free(string);
    if(strcmp(ex->value,"18killstreak") == 0){
        return " ";
    }
    string = concat(string,ex->value);
    string = concat(string,exprToString(ex->a));
    string = concat(string,exprToString(ex->b));
    return string;
}

这是一个示例表达式:z /(x + 2 * x) 这是打印输出:0etur / z + x * 2 x

发生了什么事?

3 个答案:

答案 0 :(得分:2)

问题中当前代码中存在(至少)两个严重错误:

char* exprToString(Expr* ex){
    char *string = (char*)malloc(sizeof(char));
    //free(string);
    if(strcmp(ex->value,"18killstreak") == 0){
        return " ";
    }
    string = concat(string,ex->value);
    string = concat(string,exprToString(ex->a));
    string = concat(string,exprToString(ex->b));
    return string;
}

首先,也是最微妙的,是你有时(大部分)返回指向已分配内存的指针,但有时会返回指向常量字符串的指针。这意味着调用代码无法在返回值上可靠地调用free(),这会导致返回指针被释放时导致细微(或可能不那么微妙)的内存损坏,或者导致泄漏,因为返回的指针永远不会被释放。两者都不令人满意。确保如果返回指针,它可以统一释放。考虑返回NULL指针而不是单空字符串,但如果必须返回单空字符串,请确保分配它。请注意,早期返回也会完全不必要地泄漏内存。

其次,更严重的是,您必须保留指向concat返回的内存的指针,这样您就可以释放除返回给调用者的值以外的所有内容。

第一个问题出在:

char *string = (char*)malloc(sizeof(char));

您不检查内存分配是否成功。你不能确保你有一个字符串。您需要*string = '\0';才能安全。

然后你有:

string = concat(string, ex->value);

在此之后,您丢失了第一个malloc()分配的内存的唯一指针 - 这是内存泄漏的定义。接下来的两行犯了同样的错误;你回来之前已经泄漏了3块内存。

对此的一个解决方案是:

char* exprToString(Expr* ex){
    if (strcmp(ex->value, "18killstreak") == 0){
        return 0;
    }
    char *string = (char*)malloc(sizeof(char));
    if (string == 0)
        return 0;
    *string = '\0';
    char *t1 = concat(string, ex->value);
    free(string);
    string = t1;
    t1 = concat(string, exprToString(ex->a));
    free(string);
    string = t1;
    string = concat(string, exprToString(ex->b));
    free(string);
    string = t1;
    return string;
}

开始看起来令人不快重复。另一种解决方案是更改concat() - 代码和语义。如果要求第一个指针是指向可释放(可重新分配)内存的指针,则可以使用:

char *concat(char *s1, const char *s2)
{
    if (s1 == 0)
        return 0;
    size_t len_1 = strlen(s1);
    size_t len_2 = strlen(s2);
    char *result = realloc(s1, len_1 + len_2 + 1);
    if (result == 0)
    {
        free(s1);
        return 0;
    }
    strcat(result, s2);
    return result;
}

现在您可以使用原始代码的略微修改版本:

  char* exprToString(Expr* ex){
    if (strcmp(ex->value, "18killstreak") == 0){
        return 0;
    }
    char *string = (char*)malloc(sizeof(char));
    if (string == 0)
        return 0;
    *string = '\0';
    string = concat(string, ex->value);
    string = concat(string, exprToString(ex->a));
    string = concat(string, exprToString(ex->b));
    return string;
}

现在只有更多泄漏的选项,因为exprToString()返回指向您永远不会释放的已分配内存的指针。有多种方法可以解决这个问题。其中之一是concat()被重新定义为将两个指针带到可释放(可重新分配)的内存并在返回之前释放第二个指针。然后,您必须担心电话string = concat(string, ex->value); - 例如,一个安全的替代方案是string = concat(string, strdup(ex->value));。 (至少,如果concat()函数检查空指针,则是安全的。)

我可能还没有发现其他问题。建议的代码还没有通过编译器 - 可能存在错误。特别是,我没有完全跟踪每次可能的显式分配失败后会发生什么;错误恢复代码可能存在问题。

有些人坚决反对casting the result of malloc()。如果您保证使用确保在使用malloc()之前声明import matplotlib as mpl from bokeh.models import LogColorMapper,ColorMapper,LinearColorMapper, LogTicker, ColorBar MAG = HoverTool(tooltips=[ ("frequency", "$x"), ("Impedance", "@y"), ]) Err = HoverTool(tooltips=[ ("frequency", "$x"), ("Error", "@y"), ]) E=zeros(154) COLORS = Spectral5 #error = (abs(Zc-ZZ)/ZZ)*100 #E=[int(i) for i in error/1000] for i in range(1): error=abs(Z[3,:]-Zc[3,:])/Z[3,:] c = [ "#%02x%02x%02x" % (int(r), int(g), int(b)) for r, g, b, _ in 255*mpl.cm.viridis(mpl.colors.Normalize()(error)) ] mapper = LinearColorMapper(palette="Viridis256", low=0, high=10) #c={'field': 'y', 'transform': mapper} p1 = figure(title="Simple test", plot_height=400, plot_width=600 ,x_axis_type="log",y_axis_type="log",tools=['save',MAG,'box_zoom','reset','pan','wheel_zoom']) p1.circle(f[0,:],Zc[0,:],color=c, size=8,legend="measured") r1 = p1.line(f[0,:],Z[0,:], color="black", line_width=1.5,legend="Theortical") p1.circle(f[1,:],Zc[1,:],color=c, size=8,legend="measured") r1 = p1.line(f[1,:],Z[1,:], color="black", line_width=1.5,legend="Theortical") p1.circle(f[2,:],Zc[2,:],color=c, size=8,legend="measured") r1 = p1.line(f[2,:],Z[2,:], color="black", line_width=1.5,legend="Theortical") p1.circle(f[3,:],Zc[3,:],color=c, size=8,legend="measured") r1 = p1.line(f[3,:],Z[3,:], color="black", line_width=1.5,legend="Theortical") p1.circle(f[4,:],Zc[4,:],color=c, size=8,legend="measured") r1 = p1.line(f[4,:],Z[4,:], color="black", line_width=1.5,legend="Theortical") p1.circle(f[5,:],Zc[5,:],color=c, size=8,legend="measured") r1 = p1.line(f[5,:],Z[5,:], color="black", line_width=1.5,legend="Theortical") p1.circle(f[6,:],Zc[6,:],color=c, size=8,legend="measured") r1 = p1.line(f[6,:],Z[6,:], color="black", line_width=1.5,legend="Theortical") p1.circle(f[7,:],Zc[7,:],color=c, size=8,legend="measured") r1 = p1.line(f[7,:],Z[7,:], color="black", line_width=1.5,legend="Theortical") p1.circle(f[8,:],Zc[8,:],color=c, size=8,legend="measured") r1 = p1.line(f[8,:],Z[8,:], color="black", line_width=1.5,legend="Theortical") p1.yaxis.axis_label = 'Impedance' p1.xaxis.axis_label = 'Frequency' q1 = figure(title="simple line example", plot_height=400, plot_width=600 ,x_axis_type="log",x_range=p1.x_range,tools=[MAG,'box_zoom','reset','pan','wheel_zoom']) i=0 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) i=1 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) i=2 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) i=3 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) i=4 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) i=5 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) i=6 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) i=7 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) i=8 error=abs(Z[i,:]-Zc[i,:])/Z[i,:] rr1 = q1.circle(f[i,:],error, color=c, line_width=3) color_bar = ColorBar(color_mapper=mapper, label_standoff=12, border_line_color=None, location=(0,0)) 的选项进行编译,我就不会坚持这一点。我按照原始代码留下了演员阵容。

答案 1 :(得分:2)

这里是内存泄漏的来源:

char *string = (char*)malloc(sizeof(char));
...
string = concat(string,ex->value);
string = concat(string,exprToString(ex->a));
string = concat(string,exprToString(ex->b));

每次调用concat时,都会覆盖当前值string,其中包含指向malloc内存的指针,并带有新的指针值,并丢弃旧值。

如果第一个参数不为NULL,则应将concat更改为使用realloc。这样你就可以根据需要增加缓冲区。在初始调用concat时,您将第一个参数设置为NULL。

因此请修改concat以使用realloc

char* concat(const char *s1, const char *s2)
{
    char *result;
    if (s1) {
        result = realloc(s1, strlen(s1)+strlen(s2)+1);
        if (!result) {
            perror("realloc failed");
            free(s1);
            return NULL;
        }
        strcpy(result, s2);
    } else {
        result = malloc(strlen(s2)+1);
        if (!result) {
            perror("malloc failed");
            return NULL;
        }
        strcat(result, s2);
    }
    return result;
}

然后您将exprToString更改为:

char* exprToString(Expr* ex){
    if(strcmp(ex->value,"18killstreak") == 0){
        return strdup(" ");
    }
    char *string = concat(NULL,ex->value);
    char *a_str = exprToString(ex->a);
    char *b_str = exprToString(ex->b);
    string = concat(string,a_str);
    string = concat(string,b_str);
    free(a_str);
    free(b_str);
    return string;
}

请注意,因为此函数需要返回动态分配的内存,而基本情况不能直接返回字符串文字。调用此函数的人负责释放返回的缓冲区。

答案 2 :(得分:1)

您的concat功能没问题,但exprToString的开头是错误的:

    char *string = (char*)malloc(sizeof(char));    
    string = concat(string,ex->value);

你只分配一个1字节的字符串,它甚至不是nul终止的,所以进一步调用concat连接一些带有ex->value未定义长度的字符串。

如果你想要的是用string的副本初始化ex-value,只需将上面的内容替换为:

char *string = strdup(ex->value);

下一个问题是执行此操作时内存泄漏:

string = concat(string,exprToString(ex->a));

你正在覆盖string之前的值,所以它可以正常工作,但因为你没有机会释放输入而导致内存泄漏。

您可以更改concat,以便通过调用:

释放第一个参数
free(s1);

在返回结果之前。或者在realloc上使用s1,以便释放然后调整大小。

但两者都不是很方便。如果某个调用者没有读取文档,如果你将文字作为第一个参数传递,那么需要字符串用于其他内容会怎样?这会使你的concat函数不那么有用且容易出错(除非为了这个特殊目的而使用一个非常特殊的名称调用它)

最简单的方法是分解您的concat电话:

char *temp = concat(string,ex->value);
free(string);
string = temp;

C语言幕后没有很多魔法,比如运算符重载或垃圾收集,所以一切都必须明确写出来。