You have such a large number of small problems, it is difficult to know where to begin. First a nit, you never include spaces around "->"
when referencing a structure member. Use p->name
, not p -> name
. Continuing...
You fail to validate the return of scanf
. You must check the return Every Time, or you are tempting Undefined Behavior. You also must change "%99[^\n]"
to " %79[^\n]"
because neither "%c"
or "%[...]"
consume leading whitespace. Failing to add the " "
before %12[^\n]
would make it impossible to read p->ssn
and lead to a matching failure reading p->yearOfBirth
.
Note the change from 99
to 79
. You #define NAME_SIZE 80
and declare char name[NAME_SIZE];
, what do you think you are doing using a field-width modifier of 99
when at most 79
characters can be stored in name
? (You have the same problem with #define SSN_SIZE 13
). You use the field-width modifier with scanf
to protect your array bounds. Setting the *field-width modifier greater than your array size (-1
) removes the protection it should provide altogether.
Your failure to check the return of scanf
and handle the three cases of return necessary will lead to Undefined Behavior if the user accidentally makes a single error in input. Failure to check the return of scanf
is one of the most common pitfall new C programmer fall into. It is mandatory for every user input. Otherwise, you can have no confidence your code is actually processing valid data.
scanf
can be used, if used correctly. This means you are responsible for checking the return of scanf
every time. You must handle three conditions
(return == EOF)
the user canceled input by generating a manual EOF
by pressing Ctrl+d (or on windows Ctrl+z, but see CTRL+Z does not generate EOF in Windows 10 (early versions));
(return < expected No. of conversions)
a matching or input failure occurred. For a matching failure you must account for every character left in your input buffer. (scan forward in the input buffer reading and discarding characters until a '\n'
or EOF
is found); and finally
(return == expected No. of conversions)
indicating a successful read -- it is then up to you to check whether the input meets any additional criteria (e.g. positive integer, positive floating-point, within a needed range, etc..).
A short function implementation to empty all remaining characters in stdin
in the event of matching failure could be as simple as:
void empty_stdin (void)
{
int c = getchar();
while (c != '\n' && c != EOF)
c = getchar();
}
(implementing in your code is left as an exercise for you)
Further, using type void
as the return of an input function makes no sense. You must choose your return to provide the return of required information AND provide an indication of whether the input succeeded or failed. Using void
for getOnePerson()
means you have no way of knowing whether you received all valid input, or just received name
, but not ssn
, or if the user simply generated a manual EOF
canceling input at each prompt. A simple integer return is all you need (e.g. return 0;
on failure or return 1;
only after all 3-inputs are validated) You could do something like:
int getOnePerson (person_t *p)
{
int rtn; /* scanf return */
/* validate each input for all 3 cases */
fputs ("\nEnter full name: ", stdout); /* no need for printf, no conversion */
if ((rtn = scanf (" %79[^\n]", p->name)) != 1) {
if (rtn == EOF)
puts ("(input complete)");
else
fputs ("error: invalid format 'p->name'.\n", stderr);
return 0;
}
/* validate each input for all 3 cases */
fputs ("Enter ssn: ", stdout); /* ditto */
if ((rtn = scanf (" %12[^\n]", p->ssn)) != 1) { /* " */
if (rtn != EOF)
fputs ("error: invalid format 'p->ssn'.\n", stderr);
return 0;
}
/* validate each input for all 3 cases */
fputs ("Enter year of birth: ", stdout);
if ((rtn = scanf ("%d", &p->yearOfBirth)) != 1) {
if (rtn != EOF)
fputs ("error: invalid format 'p->yearOfBirth'.\n", stderr);
return 0;
}
return 1; /* indicates all 3 input successfully received */
}
(note: input is complete when EOF
is encountered, either manually generated by the user or encountered in the input stream)
void
is also meaningless as a return for getPeople()
. You can't use a for
loop and just assume all inputs were successful, instead, you need to take input only while input is available, while protecting your array bounds, and then return the number of input actually received (which may be less than NUM_PEOPLE
). Further, choose your type properly. For counters, size_t
is the proper type (you can't have a negative number of persons), e.g.
size_t getPeople (person_t *p, size_t numOfPeople)
{
// for(int i = 0; i < sizeof(p); i++)
// {
// getOnePerson(p[i]);
// }
size_t n = 0;
while (n < numOfPeople && getOnePerson (&p[n]))
n++;
return n;
}
When you pass an array as a parameter to a function, the array is converted to a pointer to the first element. So when you do sizeof(p)
within a function -- that is not what you want and does not provide the number of elements in the array referenced by p
-- what it does provide is sizeof(a_pointer)
, which is fixed by your compiler (e.g. 8-bytes on x86_64, 4-bytes on x86). You pass numOfPeople
-- use it, e.g.
void printPeople (person_t *p, size_t numOfPeople)
{
puts ("\nStored People\n");
// for(int i = 0; i < sizeof(p); i++)
for (size_t i = 0; i < numOfPeople; i++)
{
printOnePerson(p[i]);
}
}
You will also want to fix printf("%s\n", p.yearOfBirth);
(yearOfBirth
is not a string...)
Your header is fine, but it is missing something. Always include header guards around the content of your header files to prevent multiple inclusions of the file, e.g.
#ifndef mystructures_h
#define mystructures_h 1
...
/* your header content */
...
#endif
(note: the 1
isn't required, but if you are defining a constant, it is never a bad idea to give it an affirmative value of your choosing)
There are probably more that were corrected, but those were the major points. Putting it altogether, you could do:
structures.h
#ifndef mystructures_h
#define mystructures_h 1
#include <stdio.h>
#define NAME_SIZE 80
#define SSN_SIZE 13
#define NUM_PEOPLE 10
typedef struct {
char name[NAME_SIZE];
char ssn[SSN_SIZE];
int yearOfBirth;
} person_t;
size_t getPeople (person_t *p, size_t numOfPeople);
void printPeople (person_t *p, size_t numOfPeople);
#endif
(can you figure out why #include <stdio.h>
was moved from structures.c
into structures.h
? do you know why the function prototypes for getPeople()
and printPeople()
are required in the header and not the rest?)
structures.c
#include "structures.h"
int getOnePerson (person_t *p)
{
int rtn; /* scanf return */
fputs ("\nEnter full name: ", stdout);
if ((rtn = scanf (" %79[^\n]", p->name)) != 1) {
if (rtn == EOF)
puts ("(input complete)");
else
fputs ("error: invalid format 'p->name'.\n", stderr);
return 0;
}
fputs ("Enter ssn: ", stdout); /* ditto */
if ((rtn = scanf (" %12[^\n]", p->ssn)) != 1) { /* " */
if (rtn != EOF)
fputs ("error: invalid format 'p->ssn'.\n", stderr);
return 0;
}
fputs ("Enter year of birth: ", stdout);
if ((rtn = scanf ("%d", &p->yearOfBirth)) != 1) {
if (rtn != EOF)
fputs ("error: invalid format 'p->yearOfBirth'.\n", stderr);
return 0;
}
return 1;
}
size_t getPeople (person_t *p, size_t numOfPeople)
{
// for(int i = 0; i < sizeof(p); i++)
// {
// getOnePerson(p[i]);
// }
size_t n = 0;
while (n < numOfPeople && getOnePerson (&p[n]))
n++;
return n;
}
void printOnePerson (person_t p)
{
printf("%s:", p.name);
printf("%s:", p.ssn);
// printf("%s\n", p.yearOfBirth);
printf("%d\n", p.yearOfBirth);
}
void printPeople (person_t *p, size_t numOfPeople)
{
puts ("\nStored People\n");
// for(int i = 0; i < sizeof(p); i++)
for (size_t i = 0; i < numOfPeople; i++)
{
printOnePerson(p[i]);
}
}
A short test program peopletest.c
#include "structures.h"
int main (void) {
person_t people[NUM_PEOPLE] = {{ .name = "" }};
size_t npeople = getPeople (people, NUM_PEOPLE);
printPeople (people, npeople);
}
Example Use/Output
$ ./bin/peopletest
Enter full name: Person A. One
Enter ssn: 123456789
Enter year of birth: 2001
Enter full name: Person B. Two
Enter ssn: 234567890
Enter year of birth: 2002
Enter full name: Person C. Three
Enter ssn: 345678901
Enter year of birth: 2003
Enter full name: (input complete)
Stored People
Person A. One:123456789:2001
Person B. Two:234567890:2002
Person C. Three:345678901:2003
Look things over and let me know if you have further questions.