In the loop
for (i=0;i<n;i++){
printf ("Enter name: ");
gets(em[i].name);
}
you appear to be using em
as if it were an array of n
elements. However, it is not an array. It is only a single element.
To fix this, you could define em
as a variable-length array, like this:
int main( void )
{
int n;
printf("Number of entries: ");
scanf("%d", &n);
emp em[n];
[...]
However, not all compilers support variable-length arrays, and compilers are not required to support them by the ISO C standard.
Alternatively, you could define a fixed-length array, like this:
#include <stdio.h>
#include <stdlib.h>
#define MAX_ENTRIES 20
[...]
int main( void )
{
emp em[MAX_ENTRIES];
int n;
printf("Number of entries: ");
scanf("%d", &n);
//verify that number of entries is not so high that it would
//overflow the buffer
if ( n > MAX_ENTRIES )
{
printf( "Too many entries!\n" );
exit( EXIT_FAILURE );
}
[...]
This solution will work on all compilers, as it does not require that the compiler supports variable-length arrays.
Another issue is that in the line
gets(em[i].name);
it is your responsibility to ensure that there is sufficient memory to store the string. In your program, there is not sufficient memory to store a string of any length, because you are not even providing sufficient room for the terminating null character of the string.
In the declaration
typedef struct employee{
int code;
char name[];
} emp;
the member name
is a flexible array member. If you decide to use a flexible array member, then it is your responsiblity to ensure that there is additional memory available immediately after the object, to store the data for the flexible array member. In your program, you are not doing this. It probably was not your intention to use a flexible array member in the first place, so I will not explain any further how to use one.
In order to ensure that the name
has sufficent space for storing a string, you could specify a fixed number of characters that are reserved for storing the string, for example like this:
typedef struct employee {
int code;
char name[100];
} emp;
However, if the user enters more than 100 characters, then your program will overflow the array name
, which will invoke undefined behavior. This means that your program may crash or misbehave in some other way.
To prevent this, I recommend that you use the function fgets
instead of gets
. Note, however, that in contrast to gets
, fgets
will write the newline character into the string, so you will probably want to remove it.
Yet another issue is that the line
scanf ("%d", &n);
will leave the newline character on the input stream, which will cause the next call to gets
/fgets
to read an empty line, which is not what you want. Therefore, you must discard this newline character (and any other remaining characters on that line), for example like this:
int c;
do
{
c = getchar();
} while ( c != '\n' && c != EOF );
Here is the entire program, which does not use variable-length arrays:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_ENTRIES 20
typedef struct employee
{
int code;
char name[100];
} emp;
int main( void )
{
emp em[MAX_ENTRIES];
int n;
int c;
//read number of entries from user
printf( "Number of entries: " );
scanf( "%d", &n );
//discard remainder of line
do
{
c = getchar();
} while ( c != '\n' && c != EOF );
//verify that number of entries is not so high that it would
//overflow the buffer
if ( n > MAX_ENTRIES )
{
printf( "Too many entries!\n" );
exit( EXIT_FAILURE );
}
//read individual entries from user
for ( int i=0; i < n; i++ )
{
char *p;
printf( "Enter name: " );
fgets( em[i].name, sizeof em[i].name, stdin );
//attempt to find the newline character
p = strchr( em[i].name, '\n' );
if ( p == NULL )
{
//since we found no newline character, we must
//assume that the line was too long to fit into
//the memory buffer
printf( "Line was too long to fit into the buffer!\n" );
exit( EXIT_FAILURE );
}
//remove the newline character by overwriting it with a
//null character
*p = '\0';
}
//print back the stored strings
printf( "\nStored the following strings:\n" );
for ( int i=0; i < n; i++ )
{
printf( "%s\n", em[i].name );
}
return EXIT_SUCCESS;
}
This program has the following behavior:
Number of entries: 5
Enter name: Mike
Enter name: Alice
Enter name: Charlie
Enter name: Bob
Enter name: Jimmy
Stored the following strings:
Mike
Alice
Charlie
Bob
Jimmy
However, I generally do not recommend using scanf
for user input, because it behaves in a non-intuitive manner for line-based user input. As already mentioned, it always leaves the newline character in the buffer, and maybe also some other characters. It is a pain to always have to remove them whenever you call scanf
. Also, when using fgets
, it is a pain to always have to check for the newline character in order to verify that the entire line was read in, and to remove the newline character.
For this reason, I recommend to write your own functions which handle all of this. In my code below, I call these functions get_int_from_user
and get_line_from_user
. I have designed these functions in such a way that they also validate the input and reprompt the user, if the input is invalid.
Note that using these functions makes the function main
shorter and simpler:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
int get_int_from_user( const char prompt[] );
void get_line_from_user( const char prompt[], char buffer[], int buffer_size );
#define MAX_ENTRIES 20
typedef struct employee
{
int code;
char name[100];
} emp;
int main( void )
{
emp em[MAX_ENTRIES];
int n;
//read number of entries from user
n = get_int_from_user( "Number of entries: " );
//verify that number of entries is not so high that it would
//overflow the buffer
if ( n > MAX_ENTRIES )
{
printf( "Too many entries!\n" );
exit( EXIT_FAILURE );
}
//read individual entries from user
for ( int i=0; i < n; i++ )
{
get_line_from_user(
"Enter name: ",
em[i].name, sizeof em[i].name
);
}
//print back the stored strings
printf( "\nStored the following strings:\n" );
for ( int i=0; i < n; i++ )
{
printf( "%s\n", em[i].name );
}
return EXIT_SUCCESS;
}
//This function will attempt to read one integer from the user. If
//the input is invalid, it will automatically reprompt the user,
//until the input is valid.
int get_int_from_user( const char prompt[] )
{
//loop forever until user enters a valid number
for (;;)
{
char buffer[1024], *p;
long l;
//prompt user for input
fputs( prompt, stdout );
//get one line of input from input stream
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "Unrecoverable input error!\n" );
exit( EXIT_FAILURE );
}
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
{
int c;
printf( "Line input was too long!\n" );
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Unrecoverable error reading from input!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
//attempt to convert string to number
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "Error converting string to number!\n" );
continue;
}
//make sure that number is representable as an "int"
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "Number out of range error!\n" );
continue;
}
//make sure that remainder of line contains only whitespace,
//so that input such as "6abc" gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Unexpected input encountered!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto continue_outer_loop;
}
}
return l;
continue_outer_loop:
continue;
}
}
//This function will read exactly one line of input from the
//user. It will remove the newline character, if it exists. If
//the line is too long to fit in the buffer, then the function
//will automatically reprompt the user for input. On failure,
//the function will never return, but will print an error
//message and call "exit" instead.
void get_line_from_user( const char prompt[], char buffer[], int buffer_size )
{
for (;;) //infinite loop, equivalent to while(1)
{
char *p;
//prompt user for input
fputs( prompt, stdout );
//attempt to read one line of input
if ( fgets( buffer, buffer_size, stdin ) == NULL )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
//attempt to find newline character
p = strchr( buffer, '\n' );
//make sure that entire line was read in (i.e. that
//the buffer was not too small to store the entire line)
if ( p == NULL )
{
int c;
//a missing newline character is ok if the next
//character is a newline character or if we have
//reached end-of-file (for example if the input is
//being piped from a file or if the user enters
//end-of-file in the terminal itself)
if ( (c=getchar()) != '\n' && !feof(stdin) )
{
if ( ferror(stdin) )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
printf( "Input was too long to fit in buffer!\n" );
//discard remainder of line
do
{
c = getchar();
if ( ferror(stdin) )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' && c != EOF );
//reprompt user for input by restarting loop
continue;
}
}
else
{
//remove newline character by overwriting it with
//null character
*p = '\0';
}
//input was ok, so break out of loop
break;
}
}
Here is an example of the behavior of the program:
Number of entries: abc
Error converting string to number!
Number of entries: 6abc
Unexpected input encountered!
Number of entries: 5
Enter name: Mike
Enter name: Alice
Enter name: This is a very long line that is too long to fit into the buffer. This is a very long line that is too long to fit into the buffer.
Input was too long to fit in buffer!
Enter name: Charlie
Enter name: Bob
Enter name: Jimmy
Stored the following strings:
Mike
Alice
Charlie
Bob
Jimmy
As you can see, when invalid input was entered, the input was discarded and the user was reprompted for input.