Reputation: 145
After having a segmentation fault in a C program I am developing, I realized the error came from my declaration of a variable.
It is the first time I tried to declare a variable of data type Ens_participants defined below:
typedef struct
{
int cardinal;
Participant tab[NB_MAX_PARTICIPANTS];
} Ens_participants;
And Participant is another data type I created defined below:
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;
With TMAX_MOT, NB_MAX_PARTICIPANTS, and LMAX being constants :
#define TMAX_MOT 250
#define LMAX 500
#define NB_MAX_PARTICIPANTS 1000
This is the line that cause my segmentation fault:
Ens_participants les_participants;
Did I properly create and declare these variables? How can a single declaration cause a segmentation fault? If it helps, with the gdb debugger, I was told that the error came two lines before this declaration :
int ligne_valide = 1;
However, this is false as the program worked well with the above line. It is as soon as I tried to declare this new variable that I started having issues.
When I changed the value of the constant NB_MAX_PARTICIPANTS to 10 instead of 1000, the program compiled perfectly.
I apologize for this question since, as @alk suggested, my problem lies elsewhere. I have rewritten just the types of the program and experimented. The problem is not where I thought it was. The variable definitions and declarations are not causing any issues in the new program.
Upvotes: 1
Views: 4796
Reputation: 123458
Edit 2
Here's a very simplistic example in C++, pretty much off the top of my head and untested, so there are undoubtedly going to be some errors.
We're going to use the standard map
, vector
, and string
data types for
storing our participant data. The map
type is an associative data structure,
storing items indexed by an arbitrary key. A vector
is a linear container
like an array, except that its size is not fixed, and a string
is, well, a string. All of these types do their own memory management, so you don't have to worry about allocating or freeing memory as items are added or removed.
First, we need define the Participant
type:
#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;
}
};
Once the Participants
type is defined, we can instantiate our map structure:
#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;
Once that's all done, we can read data from our input file:
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 ) );
}
To find a specific entry in the map, use the find
method:
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
To iterate through the map, do the following:
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;
}
The find()
, begin()
, and end()
methods return iterators, which look and behave a lot like pointers. The iterator returned by the end()
function serves basically the same purpose as the NULL
pointer value; it acts as a well-defined "there is no data here" value.
Notice that the bulk of the work is setting up the Participant
type. Storing and accessing the data is simply a matter of using the built-in map
container and its methods.
I'll leave it there, since I don't know exactly what you want to do. Again, none of that code is tested, so there are almost certainly some errors, and the style isn't the best in the world. But, the main intent is to show you how C++ can make this kind of application much easier to write than in C. For one thing, you don't have to specify an explicit size for anything; the strings, vectors, and the map will grow or shrink as necessary without any action on your part. There are some even slicker tools in the standard library, but that should be enough to get you started.
Make sure you find a good C++ reference before starting, though.
Edit
Based on additional comments from the OP, I'm going to suggest that he look at a different language for implementing this system. C is pretty much the worst language to implement a system like this, especially if you're not that experienced in programming. Even C++ would make this task much easier. It might be worth investigating what Python has to offer.
Original
A single instance of Ens_participants
is almost 240 MiB wide. Most systems that I'm familiar with will yak if you try to allocate an object that large as an auto
(local) variable.
Before doing anything else, do some analysis and figure out how big TMAX_MOT
, LMAX
, and NB_MAX_PARTICIPANTS
really need to be. Do you really expect to have 1000 participants? Do you really expect any of your participant names to be 250 characters long, or for them to have email addresses that long? Do you really expect any of your participants to be able to read or speak 50 different languages (and for any of the language names to be 250 characters long)? And so on.
If that analysis shows that yes, your arrays really need to be that big, then there are several ways to get around this problem, in increasing order of difficulty, and all with significant disadvantages:
static
keyword to the declaration of les_participants
:
static Ens_participants les_participants;
This will set aside storage from somewhere other than the runtime stack (typically from within the program image, meaning your executable may get a little bigger). The disadvantage of this is that only a single instance of les_participants
is ever created, and it's created as soon as the program starts; you're not creating a new instance of the variable every time you enter the function declaring it. If you only ever intend to have a single instance of it, though, this is probably the easiest way to go.
If you need to pass it to any function, you will have to pass a pointer to it whether the called function needs to modify the contents of it or not.
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 );
...
}
les_participants
dynamically using malloc
or calloc
. Such a large request isn't guaranteed to succeed, though, especially if your heap is badly fragmented:
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 );
}
}
tab
element of Ens_participants
as an array of pointers to Participant
, and dynamically allocate each instance of Participant
as necessary:
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] " );
}
When you're done, you'll need to clean up after yourself:
while ( les_participants.cardinal )
free( les_participants.tab[--les_participants.cardinal] );
You've traded the iffiness of the single large memory allocation for managing a bunch of smaller memory allocations. The code is a bit more complex now, with more points of failure.
tab
element of Ens_participants
as a list or tree, rather than an array. This is a lot more work, and you may not be up for it (even a simple example would make this already-too-big answer unreadably huge). Like the previous suggestion, you're making a bunch of smaller memory allocation requests rather than a single very large allocation request, with a bonus that you can order your entries as they're being inserted. Extra bonus, you're not limited to a fixed number of entries (as in the previous suggestion). This significantly increases the amount and complexity of the code you have to write, however.
Upvotes: 2
Reputation: 754
It's probably just that your variable is too big for the available stack size on your platform. The code is technically fine for C (so, not a compile-time error), but in practice the operating system does not reserve enough stack space to make this possible.
After all, your langues_parles field takes, on its own, 250 * 500 bytes of space; i.e. 125kB. You've got three fields like that, and then some other fields, so each instance of the structure takes around 380kB.
Now, you haven't shown the value of NB_MAX_PARTICIPANTS, but my guess is that 380kB * NB_MAX_PARTICIPANTS is just too big. For example, on Windows, the default stack size is only 1MB, so if NB_MAX_PARTICIPANTS is bigger than 2, then the variable is too big (and that's assuming there is nothing else on the stack).
You will have to allocate your structure on the heap using malloc() or a similar function:
Ens_participants* les_participants = malloc(sizeof(Ens_participants));
/* ... */
free(les_participants);
Upvotes: 5