我正在努力学习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])将尝试释放前一循环中已释放的指针。
答案 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 ......作为尺寸)。