C语言中如何释放被malloc分配的结构体成员和结构体本身

4

当我尝试释放这个结构中的任何内容时出现问题。首先是头文件中的结构定义:

    typedef struct{
int* rType;
unsigned long int numCols;
char* rString;  //The literal string, NULL delimited
int rsSize;     //The size of the string, since NULLS can't be used to find the string end
int* colsIndex; //Where each new column starts in rString
long* iColVals;   //integer version of the column
double* dColVals; //double precision value of column
}  row_t ;

那么这里就是可以创建结构体实例的地方:
row_t* delimitLine(char* line, char* delimList, char delimListSize)
{
row_t* thisRow;
.
.
.
//Make a place for this stuff in memory
thisRow = (row_t*) malloc(sizeof(row_t));
if(thisRow==NULL) return NULL;
.
.
.
thisRow->rString = line;

//Make Row Mem

    //colsIndex
    thisRow->colsIndex = (int*) malloc(numCols*sizeof(int));
    if(thisRow->colsIndex==NULL) return NULL;   

    //rType
    thisRow->rType = (int*) malloc(numCols*sizeof(int));
    if(thisRow->rType==NULL) return NULL;   

    //iColVals
    thisRow->iColVals = (long*) malloc(numCols*sizeof(long));
    if(thisRow->iColVals==NULL) return NULL;    

    //dColVals
    thisRow->dColVals = (double*) malloc(numCols*sizeof(double));   
    if(thisRow->dColVals==NULL) return NULL;    
.
.
.
return thisRow;

那么这里是如何创建“line”的:

char* RBreadLine(fifo_t* fifo)
{
char* outbuf = NULL;
.
.
.
outbuf = (char*) malloc(sizeof(char)*(cnt+1));
.
.
.
return outbuf;
}

最后是调用序列:
main()
{
row_t* row = NULL;
.
.
.
while(kg>=0)
{
//test condition to exit loop not shown
line = RBreadLine(fifo);
.
.
.

 row = delimitLine(line, delimList, delimListSize);
//some code to manipulate the row data here
 printRow(row);
 rowDestructor(row);



}

}

当我将对rowDestructor的调用注释掉时,程序按预期运行,但是如果尝试释放某些东西,则会崩溃。我已经尝试注释掉除结构体中的单个成员之外的所有行,并仍然崩溃,所以在这里做错了什么。
这个程序的想法是有一个大型文本文件处理程序,逐行读取并让我做一些操纵行数据的事情,然后printRow()输出最终结果。
简单的测试用例是将值作为它们从delimitLine函数(它只分配内存,并使用数据填充行结构)中出现的方式输出。
当此过程完成时,我应该希望释放内存并重新开始。如果我不调用析构函数而是每次调用RBreadLine()和delimitLine()时孤立这些指针,则该程序按预期工作。
如果我调用rowDestructor(),则程序在第一行之后崩溃(在第一次调用rowDestructor()时)。
 Now to start outputting line
    1)  2)  3)  4)  5)  6)  7)  8)  9)  10)  (-10)63.116722551236001948     0       0       0       0       1       1       1       1       1       0    1

    Aborted (core dumped)

也许我对于malloc()或free()有些不理解,但看起来如果我可以在没有错误和崩溃的情况下访问struct成员的有效数据,那么free就应该能够释放已经malloc的内存。也许我只是在到处传递这些指针(比如将指向“line”的指针传递给分配给struct成员的函数),但在我的想法中,这一切都得到了很好的解决。我知道struct的每个成员都被malloc了,所以在我从下往上工作时应该能够释放所有内容然后释放struct。然后我可以再把另一个指针放到row struct上,再次开始工作。 我这样做的原因是因为我想要能够处理极大的数据集。这是一个结构性重写程序,它过去使用fread将所有内容加载到内存中,然后进行处理,但我的某些数据集会导致计算机内存不足……所以我将采取块处理方法。 一旦我成功地释放了一行,那么我就可以通过创建一个row_t**来构建它,其中我可以使用FIFO缓冲概念将行循环到row_t**上,这将允许我适当地向前和向后查找文本文件(例如用于应用FIR滤波器),但不需要将整个文件加载到内存中。 例如,一个行FIFO将把这些新的row_t*结构存储到row_t**上,我在填充循环缓冲区并开始覆盖旧指针后释放旧指针……这就是我要做的事情。 我认为对于这个问题有一个答案将会是我理解malloc()和free()的突破,或者可能是关于指针和结构体方面的一些明确的东西。 谢谢任何回复。 编辑:对于我的问题忽略了最重要的部分,请原谅。
void rowDestructor(row_t* thisRow)
{
    //rString
    free(thisRow->rString);

    //colsIndex
    free(thisRow->colsIndex);   

    //rType
    free(thisRow->rType);   

    //iColVals
    free(thisRow->iColVals);    

    //dColVals
    free(thisRow->dColVals);

    //finally kill the whole thing
    free(thisRow);
}

还有其他人提到了编译器标志,这是我正在使用的:

gcc  -Wall laproc.c utils.c csvsurgeon.c -lm -o csv-surgeon

(laproc.c 是我特定的信号处理代码,需要链接数学库,但在本例中,我将其简化为不调用这些函数以排除它们)

我使用的是这个版本的 gcc:

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-pc-cygwin/4.5.3/lto-wrapper.exe
Target: i686-pc-cygwin
Configured with: /gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3/configure --srcdir=/gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gc
c-4.5.3 --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --libexecdir=/usr/lib --datadir=/usr/share --localstatedir=/var --sysco
nfdir=/etc --datarootdir=/usr/share --docdir=/usr/share/doc/gcc4 -C --datadir=/usr/share --infodir=/usr/share/info --mandir=/usr/share/man -v --with-g
mp=/usr --with-mpfr=/usr --enable-bootstrap --enable-version-specific-runtime-libs --libexecdir=/usr/lib --enable-static --enable-shared --enable-shar
ed-libgcc --disable-__cxa_atexit --with-gnu-ld --with-gnu-as --with-dwarf2 --disable-sjlj-exceptions --enable-languages=ada,c,c++,fortran,java,lto,obj
c,obj-c++ --enable-graphite --enable-lto --enable-java-awt=gtk --disable-symvers --enable-libjava --program-suffix=-4 --enable-libgomp --enable-libssp
 --enable-libada --enable-threads=posix --with-arch=i686 --with-tune=generic --enable-libgcj-sublibs CC=gcc-4 CXX=g++-4 CC_FOR_TARGET=gcc-4 CXX_FOR_TA
RGET=g++-4 GNATMAKE_FOR_TARGET=gnatmake GNATBIND_FOR_TARGET=gnatbind --with-ecj-jar=/usr/share/java/ecj.jar
Thread model: posix
gcc version 4.5.3 (GCC)

也许这就是我的问题......我已经使用以下其他gcc版本重新编译了代码:
Using built-in specs.
COLLECT_GCC=C:\Program Files\CodeBlocks\MinGW-newer\bin\gcc.exe
COLLECT_LTO_WRAPPER=c:/program files/codeblocks/mingw-newer/bin/../libexec/gcc/mingw32/4.5.2/lto-wrapper.exe
Target: mingw32
Configured with: ../../src/gcc-4.5.2/configure --build=mingw32 --enable-languages=c,c++,ada,fortran,objc,obj-c++ --enable-threads=win32 --enable-libgo
mp --enable-lto --enable-fully-dynamic-string --enable-libstdcxx-debug --enable-version-specific-runtime-libs --with-gnu-ld --disable-nls --disable-wi
n32-registry --disable-symvers --disable-werror --prefix=/mingw32tdm --with-local-prefix=/mingw32tdm --enable-cxx-flags='-fno-function-sections -fno-d
ata-sections' --with-pkgversion=tdm-1 --enable-sjlj-exceptions --with-bugurl=http://tdm-gcc.tdragon.net/bugs
Thread model: win32
gcc version 4.5.2 (tdm-1)

还有另一个版本

Using built-in specs.
Target: mingw32
Configured with: ../../gcc-4.4.1/configure --prefix=/mingw --build=mingw32 --enable-languages=c,ada,c++,fortran,objc,obj-c++ --disable-nls --disable-w
in32-registry --enable-libgomp --enable-cxx-flags='-fno-function-sections -fno-data-sections' --disable-werror --enable-threads --disable-symvers --en
able-version-specific-runtime-libs --enable-fully-dynamic-string --with-pkgversion='TDM-2 mingw32' --enable-sjlj-exceptions --with-bugurl=http://www.t
dragon.net/recentgcc/bugs.php
Thread model: win32
gcc version 4.4.1 (TDM-2 mingw32)

使用4.4.1版本时,运行过程中会出现不同的段错误。有几次运行没有发生段错误,因此可能存在编译器问题。由于我使用的是cygwin,所以编译器可能会混用实用程序和链接器(对“/bin”使用错误的目录)。
我希望已经包含了足够的内容,让人们清楚我在使用malloc分配指针后正在做什么,现在我已经包含了rowDestructor()代码。感谢到目前为止的评论。
如果我的C实现本质上有任何问题,我希望能解决它。同时,我将清理开发环境,并确保正确路径到所有组件,看看能否获得更好的结果。

4
rowDestructor的代码在哪里? - Joshua Taylor
4
虽然这不是你具体遇到的问题,但是请注意:(1)在C语言中,不要将malloc函数的返回值转换为其他类型,因为这样会掩盖一些微妙的错误;(2)不要乘以sizeof(char),因为它从不需要且只会让代码变得混乱。 - paxdiablo
这里的每个人都说不要强制类型转换你的malloc,但没有好的理由不这样做。 “哦,不,如果你不包含它,你直到运行时才能发现它”..谁关心呢。强制类型转换你的malloc,并且你的代码很可能会在c++编译器中编译,这在大多数情况下非常有用。我从未见过任何一个坚实的理由不强制类型转换它们。 - xaxxon
1
使用最多的警告(对于GCC为-Wall)编译您的代码,并修复代码,直到没有更多的警告弹出。然后使用符号(对于GCC为-g)进行编译,并使用Valgrind运行应用程序。修复代码,直到Valgrind不再给出任何警告/错误。 - alk
chux - 基本结构如下:RBReadline 很类似于 fgets(),只不过我自己制作了一个 fifo,以便“定制”行结束字符(例如,“;”而不是“\n”)。它计算 fifo 中的字符数,直到有一个换行符或序列,然后分配足够的空间(使用 malloc())来存储这么多字符。然后,一个循环将字符从 fifo 复制到“outbuf”中,并确保 outbuf[N-1] = '\0'(N 是字符计数“cnt”)。 - RJB
显示剩余5条评论
1个回答

3

事实证明我的内存管理没问题(难怪我要拔光自己的头发)。我在Linux上编译了这个程序,并很快发现了一个分配内存时的“偏移1”错误,因此我访问了一些超出范围的内存。该程序在Linux上立即崩溃,而Windows则会让程序运行一段时间后才终止。

对于那些感兴趣的人,以下是问题所在:

 row_t* delimitLine(char* line, char* delimList, char delimListSize)
{
//<Analyze "line" to count number of data fields>
.
.
.

这是以前的做法:

//Populate Row Data 
thisRow->numCols = numCols+1;   

这是如何解决的:

//Populate Row Data 
numCols+=1;
thisRow->numCols = numCols; 

这是一个问题的提示,关于为什么会出现这种情况。
.
.
.
    //colsIndex
    thisRow->colsIndex = (int*) malloc(numCols*sizeof(int));
    if(thisRow->colsIndex==NULL) return NULL;   

    //rType
    thisRow->rType = (int*) malloc(numCols*sizeof(int));
    if(thisRow->rType==NULL) return NULL;   

    //iColVals
    thisRow->iColVals = (long*) malloc(numCols*sizeof(long));
    if(thisRow->iColVals==NULL) return NULL;    

    //dColVals
    thisRow->dColVals = (double*) malloc(numCols*sizeof(double));   
    if(thisRow->dColVals==NULL) return NULL;

在程序的后面,thisRow->numCols被用来访问内存,而该内存是使用"numCols"分配的,这将比程序试图访问的要少一个元素。

显然,不良的内存访问给free()在释放这个内存时带来了麻烦。有趣的是,在Linux上,这立即生成了一个分段错误,这就是为什么我更容易地找到它的原因。

我可以把这归结为一种教训,即有时候在提问时,即使发布“不相关”的代码也很重要。

我自己解决了这个问题,但感谢大家的建议。事实上,没有人指出明显的“你的缺陷在这里”,这促使我深入挖掘,并注释了这里未发布的代码部分。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接