realloc重叠指针与不同的

时间:2016-04-20 15:39:50

标签: c pointers memory struct

我正在努力学习C并找到一个很好的练习,这给我带来了一些问题。特别是我写了以下代码:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "trip.h"

struct accounting{
    char ** people;
    char ** descriptions;
    float * amountMoney;
    int * payers;
    int participants;
    int payments;
};

struct accounting *accountTable;

void trip_initialize(){
    accountTable = malloc(sizeof(struct accounting));
    accountTable->people = malloc(sizeof(char *));
    accountTable->descriptions = malloc(sizeof(char *));
    accountTable->amountMoney = malloc(sizeof(float *));

    accountTable->amountMoney = malloc(sizeof(float *));
    accountTable->payers = malloc(sizeof(int *));
    accountTable->participants = 0;
    accountTable->payments = 0;
};

void trip_shutdown(){

    for (int i = 0; i < accountTable->participants; i++){
        free(accountTable->people[i]);
    }

    for (int i = 0; i < accountTable->payments; i++){
        free(accountTable->descriptions[i]);
    }
    free(accountTable->amountMoney);
    free(accountTable->payers);
    free(accountTable->people);
    free(accountTable->descriptions);
    free(accountTable);
};

int trip_add_person(const char * name){
    accountTable->people = realloc(accountTable->people,sizeof(accountTable->people) + sizeof(char *));
    if (!accountTable->people){
        return -1;
    }
    accountTable->people[accountTable->participants] = malloc(sizeof(char *));
    accountTable->people[accountTable->participants] = strdup(name);
    return accountTable->participants++;
};

int trip_find_person(const char * name){
    for (int i = 0; i < accountTable->participants; i++){
        if (!strcmp(accountTable->people[i], name)){
            return i;
        }
    }
    return -1;
};

int trip_add_expense(const char * descr, float amount, int payer){
    if (payer < 0 || payer > accountTable->participants){
        return 0;
    }
    accountTable->descriptions = realloc(accountTable->descriptions, sizeof(accountTable->descriptions) +  sizeof(char *));
    if (!accountTable->descriptions){
        return 0;
    }
    accountTable->amountMoney = realloc(accountTable->amountMoney, sizeof(accountTable->amountMoney) + sizeof(float *));
    if (!accountTable->amountMoney){
        return 0;
    }
    accountTable->payers = realloc(accountTable->payers, sizeof(accountTable->payers) + sizeof(int *));
    if (!accountTable->payers){
        return 0;
    }
    accountTable->descriptions[accountTable->payments] = strdup(descr);
    accountTable->amountMoney[accountTable->payments] = amount;
    accountTable->payers[accountTable->payments] = payer;
    accountTable->payments++;
    return 1;
};

现在,我承认我在确切了解指针和内存分配的工作方式时遇到了一些问题,而且我知道可能有更有效的方法来进行此练习。

我的问题是,如果我将一些人插入系统,一切正常,如果我插入3个或更多费用,人们[2]以后的描述指针将指向描述的相同记忆位置。例如。如果我添加2费用一切都还可以,如果我再添加一个新添加的人的指针将指向描述[0]的内存位置,我插入的下一个人将指向描述[1]等等覆盖名称,如果我添加更多人,我将覆盖以前保存的描述。

我真的不明白为什么会发生这种情况,因为我读到的realloc()不会覆盖其他malloc已经使用的其他内存位置。

如果我打印指针,我会得到

person pointer[0] 0x7ff73b404a20
person pointer[1] 0x7ff73b404a28
person pointer[2] 0x7ff73b404a30

description pointer[0] 0x7ff73b404a30
description pointer[1] 0x7ff73b404a38
description pointer[2] 0x7ff73b404a40 

你可以看到人[2]指向与描述[0]相同的位置,如果我继续人[3]将指向描述[1],依此类推。

我想我在某个地方引入了一些未定义的行为,但我真的不明白在哪里。

P.S。如果您尝试复制并粘贴此代码,trip_shutdown()将无效,因为free(accountTable-&gt; description [0])将尝试释放前一循环中已释放的指针。

1 个答案:

答案 0 :(得分:1)

转录评论

  

报价来自John Smith

oldptr = realloc(oldptr, newsize);这样的任何行都是等待发生的内存泄漏。如果重新分配失败,您刚刚用oldptr覆盖了NULL,因此您无法释放仍然分配的内存。始终使用:

newptr = realloc(oldptr, newsize);
if (newptr != 0) { oldptr = newptr; oldsize = newsize; }
else { …handle error… }

(如果您正在跟踪尺寸 - 或oldcount = newcount;如果您正在计算数组条目。)

  

从我的理解oldptr被NULL覆盖并释放NULL并不做任何事情;这就是为什么我检查它是否为NULL并返回0(如果是)(在这种情况下明确声明返回0的练习)。我认为当realloc返回NULL时,旧指针将自动释放,但显然情况并非如此。我会牢记这一点!

我认为您在realloc()的{​​{1}}来电时遇到问题。例如,您没有考虑已经分配的项目数量 - trip_add_expense()没有达到您认为的效果。您应该分别跟踪每个数组(或数组)中的指针数量,并使用它和条目大小等来调整大小。

sizeof(accountTable->descriptions)

和错误检查,除了增长1也不是最理想的。

调用size_t new_size = accountTable->num_desc + 1; char **new_desc = realloc(accountTable->descriptions, (new_size * sizeof(*accountTable->descriptions)); 没有任何害处(但也没有任何好处),但free(NULL)指向的内存在oldptr失败时未被realloc()释放,但您无法再通过指向free()的指针,因为它被覆盖了。这是内存泄漏。 (它不是代码中唯一的泄漏。)

  

所以,我使用realloc执行accountTable->description + sizeof(char *)因为我认为我需要一个额外的char *,我后来用它来存储指向描述字符串开头的指针。这是错的,我需要考虑我已存储的描述数量吗?另外我知道1增长是次优的,它的大小应该是Python对数组的两倍(如果我没记错的话)但是因为这对我来说更像是指针的练习我喜欢这样做更快写它

其中一个问题是你总是分配相同的空间;大小是固定的。除非你处理可变长度数组(你不是),sizeof(x)是一个编译时常量。

  

所以,告诉我,如果我理解正确的话。无论我写accountTable->description = realloc(accountTable->description + sizeof(char *))多少次,结果总是sizeof(accountTable->description) + sizeof(char *),因为sizeof是编译时常量对吗?这可以解释为什么它会超过2个条目;第一个是有效的,因为我做了初始malloc,第二个是因为我将大小增加了sizeof(char *)

是;这基本上是对的。对于第三个条目,您将为分配给两个条目分配尽可能多的空间,对于4,5,6个条目,您将分配与2个条目相同的空间。如果您尝试使用您认为已分配但未实际分配的额外条目,则这不会带来快乐。您可以通过打印您传递给分配函数的大小值来演示。你怎么知道有多少人,描述,金额和付款人(每个阵列有多大)?

代码

这大致是你需要做的。请注意,除了在问题评论中已经提到的错误之外,我还注释了一些额外的错误。

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

struct accounting
{
    char **people;
    char **descriptions;
    float *amountMoney;
    int *payers;
    int participants;
    int payments;
};

struct accounting *accountTable;

extern int trip_add_person(const char *name);
extern int trip_find_person(const char *name);
extern void trip_shutdown(void);
extern void trip_initialize(void);
extern int trip_add_expense(const char *descr, float amount, int payer);

void trip_initialize(void)
{
    accountTable = malloc(sizeof(struct accounting));
    assert(accountTable != 0);
    accountTable->people = malloc(sizeof(char *));
    accountTable->descriptions = malloc(sizeof(char *));
    /*accountTable->amountMoney = malloc(sizeof(float *)); // Repeated - leak! */
    accountTable->amountMoney = malloc(sizeof(float *));
    accountTable->payers = malloc(sizeof(int *));
    accountTable->participants = 0;
    accountTable->payments = 0;
    assert(accountTable->people != 0 && accountTable->descriptions != 0 &&
           accountTable->amountMoney != 0 && accountTable->payers != 0);
}

void trip_shutdown(void)
{
    for (int i = 0; i < accountTable->participants; i++)
        free(accountTable->people[i]);
    for (int i = 0; i < accountTable->payments; i++)
        free(accountTable->descriptions[i]);
    free(accountTable->amountMoney);
    free(accountTable->payers);
    free(accountTable->people);
    free(accountTable->descriptions);
    free(accountTable);
}

int trip_add_person(const char *name)
{
    size_t new_num = accountTable->participants + 1;
    char **new_acct = realloc(accountTable->people, new_num * sizeof(*accountTable->people));
    if (new_acct == 0)
    {
        fprintf(stderr, "Out of memory (%zu bytes requested)\n", new_num * sizeof(*accountTable->people));
        return -1;
    }
    accountTable->people = new_acct;
    /*accountTable->people[accountTable->participants] = malloc(sizeof(char *)); // Leak with strdup! */
    accountTable->people[accountTable->participants] = strdup(name);
    return accountTable->participants++;
}

int trip_find_person(const char *name)
{
    for (int i = 0; i < accountTable->participants; i++)
    {
        if (!strcmp(accountTable->people[i], name))
        {
            return i;
        }
    }
    return -1;
}

int trip_add_expense(const char *descr, float amount, int payer)
{
    if (payer < 0 || payer > accountTable->participants)
    {
        return 0;
    }
    size_t new_num = accountTable->payments + 1;
    char **new_desc = realloc(accountTable->descriptions, new_num * sizeof(*accountTable->descriptions));
    if (new_desc == 0)
    {
        fprintf(stderr, "Out of memory (%zu bytes requested)\n", new_num * sizeof(*accountTable->descriptions));
        return 0;
    }
    accountTable->descriptions = new_desc;
    float *new_money = realloc(accountTable->amountMoney, new_num * sizeof(*accountTable->amountMoney));
    if (new_money == 0)
    {
        fprintf(stderr, "Out of memory (%zu bytes requested)\n", new_num * sizeof(*accountTable->amountMoney));
        return 0;
    }
    accountTable->amountMoney = new_money;
    int *new_payers = realloc(accountTable->payers, new_num * sizeof(*accountTable->payers));
    if (new_payers == 0)
    {
        fprintf(stderr, "Out of memory (%zu bytes requested)\n", new_num * sizeof(*accountTable->payers));
        return 0;
    }
    accountTable->payers = new_payers;
    accountTable->descriptions[accountTable->payments] = strdup(descr);
    accountTable->amountMoney[accountTable->payments] = amount;
    accountTable->payers[accountTable->payments] = payer;
    accountTable->payments++;
    return 1;
}

int main(void)
{
    trip_initialize();
    int who_1 = trip_add_person("Original Poster");
    int who_2 = trip_add_person("Question Answerer");
    int who_3 = trip_add_person("Antibody");
    if (who_1 == -1 || who_2 == -1 || who_3 == -1)
        fprintf(stderr, "Oops - Adding people!\n");
    if (trip_add_expense("Deposit", 200.0, who_1) == 0 ||
        trip_add_expense("Deposit", 200.0, who_2) == 0 ||
        trip_add_expense("Deposit", 200.0, who_3) == 0)
        fprintf(stderr, "Oops - adding expenses!\n");

    trip_shutdown();
}

它在valgrind下干净地运行,并使用此命令(源文件名mm31.c)干净地编译:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Werror mm31.c -o mm31
$

请注意,描述,金额,付款人的三重分配是一团糟。你应该:

struct Payment
{
    char *description;
    float amount;
    int payer;
};

并且您的结构应该包含指向这些数组的指针。它将从根本上简化trip_add_expense()函数。

我认为你总是比你在数组中使用的空间多一个。通常最好将指针保留为未分配,直到您首先需要一些空间。然后,我会分配size_t new_num = (old_num + 2) * 2;单位之类的内容,并记录分配了多少单位以及正在使用多少单位。这将分配4个,然后是12个,然后是28个条目供使用(功率为2减4,发生时为+ 2处理old_num == 0;您也可以使用new_num = old_num * 2 + 2,给出你2,6,14,30 ......作为尺寸)。