为什么在有足够内存的情况下,malloc总是在同一点返回NULL?

6

我对我遇到的错误感到非常绝望。

在大学的C编程课上,我必须实现一个解析器来解析GML(图形建模语言)输入流。

成功后,解析器将一个抽象数据类型作为邻接矩阵返回给调用者,以表示该图形。

好吧,解析器运行得很完美,如果不是最近几天一直困扰着我,就不会有问题了。在解析器中,有一个函数调用,它反过来调用malloc。当扫描程序逐个符号传递给解析器时,malloc经常被调用。但是,在离开扫描程序例程之前,始终通过调用free()释放malloc分配的内存块。

但是,在解析器深入的一个致命函数调用中,又调用一个使用malloc来保留12个字节内存(三个整数属性)以保存结构体的函数。该结构体用于存储图中单个边的信息(源节点、目标节点、权重)。

这个调用被执行了两次。第一次,一切正常。然后,根据gml语法可以出现1到n条边,代码进入while循环,在其中将相同的指针分配给指向新Edge Struct的指针,只要在输入流中找到边。循环中的Edge识别例程的第一个调用,总共是第二个调用(第一个调用发生在进入循环之前,请参见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(即 scanner,用于向解析器提供符号):

/**
* 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] = '\0';
      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] = '\0';
      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=='\0'){

      symbolCode = eot;
   }
   /*neither terminal symbol nor end of text found on current position --> illegal symbol*/
   else{
      symbolCode = error;
   }

   free(currentIdentifier);
   free(currentNumber);
   return symbolCode;

}

现在是“Edge”识别例程中致命调用。首先,结构体的头部。

#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;
}

我知道,那是相当多的代码 ;) 只是为了表明,一切可以释放的东西,我都已经释放了。

我在过去两天里谷歌了我的问题,当然也在这里的stack overflow上找到了数百个网站、帖子等等,都涉及到malloc返回null。它们基本上都说了同样的话:内存不足(这让人有点难以置信),或堆被分段,因此没有足够大小的内存块可用。

但是:我所请求的仅仅是12(十二)字节来存储三个int属性。这似乎太多了。

我是否超出了我不知道的某些内部限制?

非常感激您的帮助。

提前表示感谢 Roland

编辑于2012年11月24日:

谢谢你们的回答。 但是。问题必须是更基本的性质。

因为:当我测试程序的其他部分(文件I/O等)时,它们比解析器复杂得多,只需从main()调用一次,我也无法分配内存。我读取的文件大约有140字节。即使我将I/O部分与所有其他部分隔离开来进行测试,即使我将它们外包给另一个项目,我也无法从系统中获得内存。我已经重新启动了计算机,一切都是绝对的,没有任何改变。

有什么想法吗? 与此同时,我在这个项目上花费了太多时间,其中大部分时间都是用来跟踪那些可恶的内存错误…… :-(((


7
确定问题是malloc返回NULL吗?这种情况非常罕见,相反可能会发生重复释放指针的情况。尝试使用assert断言来确保malloc永远不会返回NULL。 - Ramy Al Zuhouri
8
你尝试过使用Valgrind吗? - 1''
2个回答

3
如果您怀疑应用程序使用了过多的内存或者碎片化了所有可用的内存,那么您可以在运行时检查应用程序的内存使用情况。如果它占用了所有系统内存,那么 malloc 返回 NULL 就是因为这个原因。
如果上述不是您的情况,那么我会检查您的应用程序是否存在堆栈损坏。通常情况下,当您覆盖堆栈结构时,会发生非常糟糕的事情。在调试模式下,编译器会添加一些额外的检查和红色区域以便检测堆栈损坏。在发布模式下,堆栈结构的破坏通常会导致访问冲突。我可以想象,在极少数情况下,堆栈结构可能会受到损坏,并且这种损坏可能会被解释为空间不足,从而导致 malloc 返回 NULL。
无论如何,我建议您使用内存调试器。Valgrind 在很多时候都救了我,但它可能在您的环境中不可用。在 stackoverflow 上有很多关于内存调试器的主题。

1
我不能说太多,只有一个观察。在GetNextSymbol()中,我看不到对读取数字位数的限制,因此存在缓冲区溢出的可能性。读取标识符也是同样的情况。
另一个问题在于Graph()函数中,失败的Edge(sourceString, currentPosition)调用在while循环中,并且据我所知,其结果从未被释放。

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