Malloc 未正确分配内存

问题描述 投票:0回答:1

我必须编写一个小型 C 程序,该程序将从 stdin 读取并使用 malloc 创建链接列表。然而,当我以某种方式编写程序时,malloc根本没有适当地管理内存。请帮助我。

阅读.c

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

#include "expr.h"

void print_list(List *exprs)
{
    for (List *p = exprs; p != NULL; p = p->next) {
        if (p != exprs) {
            printf(" ");
        }
        assert(p->expr != NULL);
        print_expr_node(p->expr);
    }
}

int main()
{
    List *exprs = get_all_expr_nodes();
    assert(exprs != NULL);
    print_list(exprs);
    printf("\n");
}

expr.h

#pragma once

typedef enum exprType {
    NONE = 0,
    NUMBER,
    ADD,
    SUB,
    MUL,
    DIV,
    END,
} ExprType;

typedef struct expr {
    ExprType type;

    union {
        int val;

        struct {
            struct expr *left;
            struct expr *right;
        } binop;
    } u;
} Expr;

typedef struct list {
    Expr        *expr;
    struct list *next;
} List;

extern Expr *get_next_expr_node();
extern List *get_all_expr_nodes();
extern void  print_expr_node(Expr *expr);

expr.c

#include "expr.h"

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

static void set_num_expr(Expr **expr, int val)
{
    (*expr)->type  = NUMBER;
    (*expr)->u.val = val;
}

static void set_op_expr(Expr **expr, char *s)
{
    switch (*s) {
    case '+':
        (*expr)->type = ADD;
        break;
    case '-':
        (*expr)->type = SUB;
        break;
    case '*':
        (*expr)->type = MUL;
        break;
    case '/':
        (*expr)->type = DIV;
        break;
    default:
        (*expr)->type = END;
    }
}

extern void print_expr_node(Expr *expr)
{
    switch (expr->type) {
    case NUMBER:
        printf("%d", expr->u.val);
        break;
    case ADD:
        printf("+");
        break;
    case SUB:
        printf("-");
        break;
    case MUL:
        printf("*");
        break;
    case DIV:
        printf("/");
        break;
    }
}

static void print_debug(Expr *expr)
{
    printf("Expr Address: %x\n", expr);
    printf("Expr Value: ");
    print_expr_node(expr);
    printf("\n\n");
}

extern List *get_all_expr_nodes(void)
{
    Expr *expr = get_next_expr_node();
    if (expr->type == END)
        return NULL;
    List *head = (List *)malloc(sizeof(List));
    head->expr = expr;
    print_debug(head->expr);
    head->next = get_all_expr_nodes();
    return head;
}

static Expr *num_expr(int val)
{
    Expr *e  = (Expr *)malloc(sizeof(Expr));
    e->type  = NUMBER;
    e->u.val = val;
    return e;
}

static Expr *op_expr(char *s)
{
    Expr *e = (Expr *)malloc(sizeof(Expr));
    switch (*s) {
    case '+':
        e->type = ADD;
        break;
    case '-':
        e->type = SUB;
        break;
    case '*':
        e->type = MUL;
        break;
    case '/':
        e->type = DIV;
        break;
    default:
        e->type = END;
    }
    return e;
}

extern Expr *get_next_expr_node(void)
{
    /* Expr *expr = (Expr *)malloc(sizeof(Expr)); */
    char s;
    scanf("%s", &s);
    int val = atoi(&s);
    if (val) {
        /* set_num_expr(&expr, val); */
        return num_expr(val);
    } else {
        /* set_op_expr(&expr, &s); */
        return op_expr(&s);
    }
    /* return expr; */
}

特别是 expr.c 中的最后一个函数,

extern Expr *get_next_expr_node(void);
,当我像上面那样编写它时,效果很好。

我将如下所示的测试用例重定向到程序中,它在末尾打印 Expr 地址和正确的链接列表(这只是按顺序排列的术语)。

t463.stdin

13
39
*
56
*
83
67
+
23
-
-

输出

Expr Address: 2b951e0
Expr Value: 13

Expr Address: 2b95200
Expr Value: 39

Expr Address: 2b95220
Expr Value: *

Expr Address: 2b95240
Expr Value: 56

Expr Address: 2b95260
Expr Value: *

Expr Address: 2b95280
Expr Value: 83

Expr Address: 2b90000
Expr Value: 67

Expr Address: 2b90020
Expr Value: +

Expr Address: 2b90040
Expr Value: 23

Expr Address: 2b90060
Expr Value: -

Expr Address: 2b90080
Expr Value: -

13 39 * 56 * 83 67 + 23 - -

然而,当我以另一种方式重写

extern Expr *get_next_expr_node(void);
时,malloc不起作用。

extern Expr *get_next_expr_node(void)
{
    Expr *expr = (Expr *)malloc(sizeof(Expr));
    char  s;
    scanf("%s", &s);
    int val = atoi(&s);
    if (val) {
        set_num_expr(&expr, val);
        /* return num_expr(val); */
    } else {
        set_op_expr(&expr, &s);
        /* return op_expr(&s); */
    }
    return expr;
}

运行相同的测试用例,我得到以下输出。

Expr Address: 1700033
Expr Value: 13

Expr Address: 1700039
Expr Value: 39

Expr Address: 1705200
Expr Value: *

Expr Address: 1700036
Expr Value: 56

Expr Address: 1705200
Expr Value: *

Expr Address: 1700033
Expr Value: 83

Expr Address: 1700037
Expr Value: 67

Expr Address: 1705200
Expr Value: +

Expr Address: 1700033
Expr Value: 23

Expr Address: 1705300
Expr Value: -

Expr Address: 1705300
Expr Value: -

23  +  + 23 67 + 23 - -

我看到的是malloc重复使用地址,有些不是字对齐的,而且一对之间的间隙不够空间,所以最终的链表是错误的,但奇迹般地完好无损。我不知道我的代码中的哪些更改导致 malloc 的行为如此出人意料。

c
1个回答
0
投票

此类内存问题的出现和消失取决于内存受到干扰的方式,例如添加对

malloc
的调用。使用 Address Sanitizer 进行编译可以使内存使用更加严格并立即发现问题。

$ cc -Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c17 -pedantic -g -fsanitize=address   -c -o test.o test.c
$ cc -fsanitize=address  test.o   -o test
$ ./test
13
=================================================================
==18673==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ff7b409ed81 at pc 0x00010c87b09a bp 0x7ff7b409ec20 sp 0x7ff7b409e3b0
WRITE of size 3 at 0x7ff7b409ed81 thread T0
39    #0 0x10c87b099 in scanf_common(void*, int, bool, char const*, __va_list_tag*)+0x13e9 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x3f099)
    #1 0x10c87bb8b in wrap_scanf+0xfb (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x3fb8b)
    #2 0x10be60f47 in get_next_expr_node test.c:135
    #3 0x10be6182c in get_all_expr_nodes test.c:152
    #4 0x10be61b23 in main test.c:177
    #5 0x7ff810177385 in start+0x795 (dyld:x86_64+0xfffffffffff5c385)

就是这个。

    char s;
    scanf("%s", &s);

这是尝试使用单个字符缓冲区进行输入,并且内存溢出。


您需要分配足够的空间,初始化缓冲区,并限制

scanf
读取的内容不能超过缓冲区。

    char s[1024] = "";
    scanf("%1023s", s);

我使用了一个大的输入缓冲区来确保读取整个输入,使用一个小的缓冲区可能会在标准输入上留下一些输入并混淆以后的读取。缓冲区将被回收。

我们必须初始化缓冲区,因为如果

scanf
失败,我们不希望
atoi
尝试读取未初始化的垃圾。

scanf
有很多问题。如果您只需要读取一行输入,请使用
fgets
。这样比较安全。

    char s[1024] = "";
    fgets(s, sizeof(s), stdin);
© www.soinside.com 2019 - 2024. All rights reserved.