为什么在C程序中声明变量时会出现分段错误?

时间:2016-06-15 19:12:15

标签: c struct segmentation-fault

在我正在开发的C程序中出现分段错误后,我意识到错误来自我声明的变量。

这是我第一次尝试声明下面定义的数据类型Ens_participants的变量:

typedef struct 
{
    int cardinal;
    Participant tab[NB_MAX_PARTICIPANTS];
} Ens_participants;

参与者是我在下面定义的另一种数据类型:

typedef struct 
{ 
    char nom[TMAX_MOT];
    char prenom[TMAX_MOT];
    char email[TMAX_MOT];
    char nationalite[TMAX_MOT];
    char langues_parles[LMAX][TMAX_MOT];
    char langues_cherches[LMAX][TMAX_MOT];
    int age;
    char sexe;
    char universite[TMAX_MOT];
    char disponible[TMAX_MOT];
} Participant; 

使用TMAX_MOT,NB_MAX_PARTICIPANTS和LMAX为常量:

#define TMAX_MOT 250
#define LMAX 500

#define NB_MAX_PARTICIPANTS 1000

这是导致我的细分错误的行:

Ens_participants les_participants; 

我是否正确创建并声明了这些变量?单个声明如何导致分段错误?如果它有帮助,使用gdb调试器,我被告知错误在此声明之前出现了两行:

int ligne_valide = 1;

然而,这是错误的,因为该程序适用于上述行。一旦我尝试声明这个新变量,我就开始遇到问题了。

更新NO1

当我将常量NB_MAX_PARTICIPANTS的值更改为10而不是1000时,程序编译完美。

更新NO2

我为这个问题道歉,因为正如@alk所说,我的问题出在其他地方。我只重写了程序的类型并进行了实验。问题不在我想象的地方。变量定义和声明不会在新程序中引起任何问题。

2 个答案:

答案 0 :(得分:5)

可能只是因为您的变量对于平台上的可用堆栈大小而言太大了。代码在技术上适用于C(因此,不是编译时错误),但实际上操作系统没有预留足够的堆栈空间来实现这一点。

毕竟,你的langues_parles字段自带250 * 500字节的空间;即125kB。你有三个这样的字段,然后是其他一些字段,所以结构的每个实例大约需要380kB。

现在,您还没有显示NB_MAX_PARTICIPANTS的值,但我的猜测是380kB * NB_MAX_PARTICIPANTS太大了。例如,在Windows上,默认堆栈大小仅为1MB,因此如果NB_MAX_PARTICIPANTS大于2,则变量太大(假设堆栈上没有其他内容)。

您必须使用malloc()或类似的函数在堆上分配您的结构:

Ens_participants* les_participants = malloc(sizeof(Ens_participants));
/* ... */
free(les_participants);

答案 1 :(得分:2)

修改2

这里是C ++中非常的简单示例,几乎不是我的头脑,而且未经测试,所以毫无疑问会出现一些错误。

我们将使用标准mapvectorstring数据类型 存储我们的参与者数据map类型是关联数据结构, 存储由任意键索引的项目。 vector是一个线性容器 像一个数组,除了它的大小不固定,string是一个字符串。所有这些类型都有自己的内存管理,因此您不必担心在添加或删除项目时分配或释放内存。

首先,我们需要定义Participant类型:

#include <string> 
#include <vector>

// By default, all members of a struct type are public.
struct Participant 
{
  std::string nom;
  std::string prenom;
  std::string email;
  std::string nationalite;
  std::vector< std::string > langues_parles;
  std::vector< std::string > langues_cherches;
  int age;
  char sexe;
  std::string universite;
  std::string disponible;

  // Generate a key for the participant based on nom and prenom.
  // The const keyword indicates that this method will not change
  // any of the non-static members of the class.
  std::string genkey() const 
  {
    return nom + ',' + prenom;
  }

  // Overload the stream input operator; we're going to assume
  // a file structure where each item is on it's own line, and where
  // the number of languages read and spoken is explicitly specified.
  // We're also going to assume the input is always well-behaved and
  // never needs validating (which is not going to be true in the real
  // world, of course
  std::istream operator>>( std::istream& s )
  {
    s >> nom;
    // repeat for prenom, email, nationalite
    int nlang;
    s >> nlang;
    for( size_t i = 0; i < nlang; i++ )
    {
      std::string lang;
      s >> lang;
      langues_parles.push_back( lang );
    }
    // repeat for langues_cherches
    // read remaining items
    return s;
  }

  // Overload the stream output operator, write in the same format
  // that we read.
  std::ostream& operator<<( std::ostream& s )
  {
    s << nom << std::endl;
    // repeat for prenom, email, nationalite;
    s << langues_parles.size() << std::endl;
    for ( std::vector< std::string >::iterator it = langues_parles.begin();
          it != langues_parles.end(); ++it )
      s << *it << std::endl;
    // repeat for langues_cherches
    // write remaining items
    return s; 
  }
};

定义Participants类型后,我们可以实例化地图结构:

#include <map>

/**
 * I normally wouldn't use a typedef here since I don't want to
 * hide the "map-ness" of the implementation, but this will save
 * some typing later on.
 */ 
typedef std::map< std::string, Participant > Ens_participants; 
Ens_participants les_participants;

完成所有操作后,我们可以从输入文件中读取数据:

std::ifstream s( data_file );
Participant p;

// use the overloaded stream input operator to read from our data file;
// keep reading until we hit EOF or an error.
while ( s >> p ) 
{
  // add the new participant to the map
  les_participants.insert( std::make_pair( p.genkey(),  p ) );
}

要在地图中查找特定条目,请使用find方法:

std::string key = "Bode,John";

Ens_participants::iterator it = les_participants.find( key );
if ( it == les_participants.end() )
  std::cerr << "Could not find John Bode in the list of participants!" << std::endl;
else
  // process entry

要遍历地图,请执行以下操作:

for ( Ens_participants::iterator it = les_participants.begin(); it != les_participants.end(); ++it )
{
  std::cout << "key  = " << it->first << std::endl;
  std::cout << "data = " << it->second;
}

find()begin()end()方法返回迭代器,其外观和行为与指针非常相似。 end()函数返回的迭代器与NULL指针值的用途基本相同;它充当定义明确的&#34;这里没有数据&#34;值。

请注意,大部分工作都是设置Participant类型。存储和访问数据只需使用内置的map容器及其方法。

我会把它留在那里,因为我不确切地知道你想做什么。同样,这些代码都没有经过测试,因此几乎肯定存在一些错误,而且风格并不是世界上最好的。但是,主要目的是向您展示C ++如何使这种应用程序比在C中更容易编写。首先,您不必为任何事物指定明确的大小;字符串,向量和地图将根据需要增大或缩小,而无需您采取任何操作。标准库中有一些更加流畅的工具,但这应该足以让您入门。

尽管如此,请确保在启动之前找到良好的 C ++参考。

修改

根据OP的其他评论,我将建议他使用不同的语言来实现此系统。 C实现这样的系统几乎是最糟糕的语言,特别是如果你没有在编程方面经验丰富的话。甚至C ++也会使这个任务更容易 。可能值得研究Python提供的内容。

原创

Ens_participants的单个实例几乎是240 MiB宽。如果您尝试分配一个大auto(本地)变量的对象,那么我熟悉的大多数系统都会牦牛。

在做其他事情之前,先做一些分析,然后弄清楚TMAX_MOTLMAXNB_MAX_PARTICIPANTS 需要多大的。你真的希望有1000名参与者吗?您真的希望您的任何参与者名称长度为250个字符,或者他们的电子邮件地址长吗?您真的希望您的任何参与者能够阅读或说出50种不同的语言(并且任何语言名称长度为250个字符)?等等。

如果分析显示是,那么你的数组真的需要那么大,那么有几种方法可以解决这个问题,增加难度的顺序,并且都有明显的缺点:

  1. static关键字添加到les_participants的声明中:
    static Ens_participants les_participants;
    
    这将从运行时堆栈以外的某个位置预留存储(通常来自程序映像,这意味着您的可执行文件可能会变得更大)。这样做的缺点是只创建了les_participants的单个实例,并且一旦程序启动就会创建它;每次输入声明它的函数时,您都不会创建变量的 new 实例。但是,如果您只打算拥有它的单个实例,这可能是最简单的方法。 如果你需要将它传递给任何函数,你将 传递指针,无论被调用的函数是否需要修改它的内容。
    void dump( Ens_participants *lp )
    {
      for ( size_t i = 0; i cardinal; i++ )
      {
        printf( "Participant %zu: nom %s, prenom %s\n", i, lp-tab[i].nom, lp->tab[i].prenom);
        // print remaining fields
      }
    }
    
    int main( void )
    {
      static Ens_participants les_participants;
      ...
      dump( &les_participants );
      ...
    }
    
  2. 使用les_participantsmalloc动态分配calloc。但是,这样的大型请求并不能保证成功,特别是如果您的堆碎片严重碎片:
    int main( void )
    {
      /**
       * calloc initializes the allocated memory to all 0s, which is
       * useful since you are going to be storing a lot of 0-terminated
       * strings
       */
      Ens_participants *les_participants = calloc( NB_MAX_PARTICIPANTS, sizeof *les_participants );
      if ( les_participants )
      {
        /**
         * use the -> operator to access members of les_participants, like so:
         */
        les_participants->cardinal = some_value;
        les_participants->tab[i].age = 24;
        ...
        /**
         * Make sure to release the memory when you are done
         */
        free( les_participants );
      }
    }
    
  3. tab的{​​{1}}元素声明为指针数组Ens_participants,并动态分配Participant的每个实例} 有必要的:
    Participant
    当你完成后,你需要自己清理:
    typedef struct 
    {
      int cardinal;
      Participant *tab[NB_MAX_ENTRIES];
    } Ens_participants;
    
    /**
     * This particular implementation assumes that participant data
     * is manually entered from standard input
     */
    Participant *newParticipant( void )
    {
      /**
       * Dynamically allocate a single instance of a Participant,
       * calloc initializes all elements to 0
       */
      Participant *p = calloc( 1, sizeof *p );
      if ( p )
      {
        printf( "nom: " );
        fgets( p->nom, sizeof p->nom, stdin );
        printf( "prenom: " );
        fgets( p->prenom, sizeof p->prenom, stdin );
        ...
      }
      return p;
    }
    
    int main( void )
    {
      Ens_participants les_participants = {0}; // make sure everything is initialized to 0 or NULL.
    
      printf( "Add participant [y/n]: " );
      while ( tolower( getchar() ) == 'y' && les_participants.cardinal < NB_MAX_ENTRIES )
      {
        les_participants.tab[les_participants.cardinal++] = newParticipant();
        printf( "Add another participant [y/n] " );
      }
    
    您已经交换了单个大内存分配的iffiness来管理一堆较小的内存分配。代码现在有点复杂,有更多的失败点。
  4. while ( les_participants.cardinal ) free( les_participants.tab[--les_participants.cardinal] ); tab元素实现为列表或树,而不是数组。这是一个很多更多的工作,你可能不会这样做(即使一个简单的例子也会让这个已经太大的答案变得难以置信)。与之前的建议一样,您需要制作一堆较小的内存分配请求,而不是一个非常大的分配请求,并且您可以在插入条目时订购您的条目。额外奖励,您不仅限于固定数量的条目(如上一个建议)。但是,这会显着增加您必须编写的代码的数量和复杂性。