我对一个无法克服的错误感到非常失望。
在大学的C编程课上,我必须为GML(图形建模语言)输入流实现一个解析器。
成功后,解析器向调用方返回一个抽象数据类型,该类型是一个邻接矩阵,作为图的表示。
好吧,解析器工作得很完美,在过去的几天里,会不会有让我绝望的问题。在解析器中,有一个函数调用,它反过来调用malloc。malloc在扫描程序逐符号向解析器传递过程中经常被调用。但是malloc的mem块总是在离开scanner例程之前通过调用free()来释放的。
但在解析器内部有一个致命的函数调用,它反过来调用一个函数,该函数使用malloc保留12字节的内存(三个整数属性)来保存结构。需要该结构来存储关于图中单个边的信息(源节点、目标节点、权重)。
这个电话打了两次。第一次,一切顺利。然后,由于根据gml语法可能会出现1到n条边,因此代码会进入while循环,在该循环中,只要在输入流中找到边,就会为同一指针分配一个指向新Edge Struct的指针。循环中边缘识别例程的第一次调用,总共是第二次调用(第一次调用发生在进入循环之前,请参见m.a.),由于malloc返回NULL而不断失败。
我根本不知道为什么。
这与内存不足无关,因为当我在该程序的main()函数中malloc 1000字节时,只是为了好玩,它工作得很好。
我使用Code::Blocks和DevCPP作为IDE。在这两种情况下,程序都会遇到相同的问题。
以下是我的主要解析例程:
DirectedGraph Graph(char* sourceString, int*currentPosition){
int sym;
int restartPosition = 0;
int* backupPosition;
char* backupString;
int nodeCount = 0;
int currentSrc = -1;
int currentTgt = -1;
int currentWgt = -1;
EdgeDescription e;
DirectedGraph correctMatrix;
MatrixStruct* errorMatrix = NULL;
/*begin parsing*/
bool isGraphHeader = GraphHdr(sourceString, currentPosition);
if(isGraphHeader == true){
bool isNode = Node(sourceString, currentPosition);
if(isNode == true){
while(isNode == true){
nodeCount++;
restartPosition = *currentPosition;
isNode = Node(sourceString, currentPosition);
}
*currentPosition = restartPosition;
/*now get edge information (from-to-weight)*/
/*as we have already read the next symbol, we have to reset*/
/*our read position by one symbol backwards*/
e = Edge(sourceString, &restartPosition); /*<======== HERE I CALL THE FATAL ROUTINE FOR THE FIRST TIME - EVERYTHING´s JUST FINE, PROGRAM PROCEEDS*/
restartPosition = 0;
/*just for clearer coding in if statement*/
currentSrc = e->source;
currentTgt = e->target;
currentWgt = e->weight;
destroyEdge(e);
if(currentSrc != -1 && currentTgt != -1 && currentWgt != -1){
/*initialize matrix with counted number of nodes*/
correctMatrix = CreateNewGraph(nodeCount);
/*the edge is inserted only when it lies within the boundaries*/
/*of our graph. but we do not interrupt the whole processing, we just skip it.*/
while(currentSrc != -1 && currentTgt != -1 && currentWgt != -1){
if(currentSrc <= nodeCount && currentTgt <= nodeCount){
InsertEdge(correctMatrix, currentSrc, currentTgt, currentWgt);
restartPosition = *currentPosition;
}
e = Edge(sourceString, currentPosition); /* <============== THIS IS THE CALL THAT FAILS*/
currentSrc = e->source;
currentTgt = e->target;
currentWgt = e->weight;
}
/*as we have read over the next symbol in the loop, reset the position to read*/
*currentPosition = *currentPosition - 1;
sym = GetNextSymbol(sourceString,currentPosition);
if(sym == rightBrace){
sym = GetNextSymbol(sourceString, currentPosition);
if(sym == eot){
return correctMatrix;
}
else{
return errorMatrix;
}
}
else{
return errorMatrix;
}
}
else{
return errorMatrix;
}
}
else{
return errorMatrix;
}
}
else{
return errorMatrix;
}
}
这里的GetNextSymbol(扫描仪,将符号传递给解析器):
/**
* DOCUMENTATION
* ============================
* This is the main scanning function
* which is used by the parser to recognize
* terminal symbols and valid literals.
*
* RETURNS: the enum code for the recognized symbol.
* or an error code, when invalid symbol encountered.
*/
int GetNextSymbol(char* sourceString, int* currentPosition){
int symbolCode;
int loopCounter = 0;
char* currentIdentifier = (char*)malloc(10);
char* currentNumber = (char*)malloc(10);
int identifierPosition = 0;
int numberPos = 0;
int numericVal = 0;
char currentChar;
currentChar = getNextChar(sourceString, currentPosition);
/*skip all blanks, empty chars,
linefeeds, carriage returns*/
while(currentChar == ' '
|| currentChar == 11
|| currentChar == 10
|| currentChar == 13
|| currentChar == 't')
{
currentChar = getNextChar(sourceString, currentPosition);
}
/*=====================================*/
/*Section 1: scan for terminal symbols */
/*====================================*/
if(currentChar == '['){
symbolCode = leftBrace;
}
else if(currentChar == ']'){
symbolCode = rightBrace;
}
/*=====================================*/
/*Section 2: scan for valid literals */
/*====================================*/
else if(isdigit(currentChar)){
/*here we calculate the numeric value of a number expression*/
/*when calculated, we assign the numeric value to the symCode variable*/
/*this works out because the values for a real symbol are always negative*/
symbolCode = digit;
while(isdigit(currentChar)){
currentNumber[numberPos] = currentChar;
currentChar = getNextChar(sourceString, currentPosition);
loopCounter++;
numberPos++;
}
currentNumber[numberPos] = ' ';
numericVal = atoi(currentNumber);
symbolCode = numericVal;
/*when identifier or braces follow number without space: reset currentPos*/
/*to the position of the previous char*/
if(isalpha(currentChar)){
*currentPosition = *currentPosition - loopCounter;
}
else if(currentChar == ']'){
*currentPosition = *currentPosition - loopCounter;
}
else if(currentChar == '['){
*currentPosition = *currentPosition - loopCounter;
}
}
else if(isalpha(currentChar)){
while(isalpha(currentChar)){
currentIdentifier[identifierPosition] = currentChar;
currentChar = getNextChar(sourceString, currentPosition);
loopCounter++;
identifierPosition++;
}
/*check wether we have found a valid identifying label*/
/*and deallocate the reserved mem space*/
currentIdentifier[identifierPosition] = ' ';
symbolCode = recognizeIdentifier(currentIdentifier);
/*when number or braces follow identifier without space: reset currentPos*/
/*to the position of the previous char*/
if(isdigit(currentChar)){
*currentPosition = *currentPosition - 1;
}
else if(currentChar == ']'){
*currentPosition = *currentPosition - 1;
}
else if(currentChar == '['){
*currentPosition = *currentPosition - 1;
}
}
else if(currentChar==' '){
symbolCode = eot;
}
/*neither terminal symbol nor end of text found on current position --> illegal symbol*/
else{
symbolCode = error;
}
free(currentIdentifier);
free(currentNumber);
return symbolCode;
}
现在是"边缘"识别程序中的致命呼叫。首先,结构的标头
#ifndef GML_EDGE_STRUCT_H_INCLUDED
#define GML_EDGE_STRUCT_H_INCLUDED
typedef struct EdgeStruct* EdgeObj;
typedef struct EdgeStruct {
int source;
int target;
int weight;
} EdgeStruct;
typedef EdgeObj EdgeDescription;
EdgeDescription createNewEdge(int src, int tgt, int wgt);
void destroyEdge(EdgeObj);
#endif // GML_EDGE_STRUCT_H_INCLUDED
的实施
#include "GML_EDGE_STRUCT.h"
#include <stdio.h>
#include <stdlib.h>
EdgeDescription createNewEdge(int source, int target, int weight){
EdgeDescription e;
int bytesRequested = sizeof(EdgeStruct);
e = malloc(bytesRequested);
e->source = source;
e->target = target;
e->weight = weight;
return e;
}
我知道,这几乎就是代码;)只是为了表明,一切可以自由的东西,我都自由了。
在过去的两天里,我在谷歌上搜索了我的问题,当然也在这里的堆栈溢出,有数百个网站、帖子等关于malloc返回null。它们基本上都是一样的:内存不足(我们称之为不太可能),或者堆碎片化,所以没有足够大小的内存块可用。
但是:我请求的全部是12个字节(大写:12个)来存储三个int属性。这似乎太过分了。
我是否超出了一些我不知道的内部限制?
我们将不胜感激。
提前感谢罗兰
编辑2012-11-24:
谢谢你的回答。但是这个问题必须具有更基本的性质。
因为:当我测试程序的其他部分(文件i/O)等时,这些部分远没有解析器复杂,并且只从main()进行一次调用,我也无法进行malloc。我读取的文件大约有140个字节。即使当我测试与所有其他部件隔离的i/O部件时,即使我将它们外包到不同的项目中,我也不会从系统中获得内存。决不是。我重新启动了电脑,一切都好。绝对地没有变化。
有什么想法吗?与此同时,我在这个项目中投入了太多的时间,其中大部分时间都在跟踪那些该死的内存错误…:-(
如果怀疑应用程序使用了太多内存或将所有可用内存分段,则可以在运行时检查应用程序的内存使用情况。如果malloc占用了所有系统内存,那么malloc因此返回NULL。
如果以上情况不是您的情况,那么我会检查您的应用程序是否存在堆损坏。通常,当您覆盖堆结构时,会发生非常糟糕的事情。在调试模式下,编译器为堆损坏检测添加了一些额外的检查和红色区域。在发布模式中,破坏堆结构通常会导致访问冲突。我可以想象,在极少数情况下,堆结构可能会被损坏,并且损坏可以被解释为空间不足,因此malloc会返回NULL。
无论如何,我会使用内存调试器。Valgrind救了我很多次,但在你的环境中可能不可用。stackoverflow上有许多关于内存调试器的主题。
我不能说太多,只有一个观察。在GetNextSymbol()
中,我看到读取的位数没有限制,因此存在缓冲区溢出的可能性。读取标识符也是如此。
另一个在Graph()
中,失败的Edge(sourceString, currentPosition)
调用处于while循环中,结果永远不会释放,AFAICS。