User Input to Array of Structs C - arrays

I am creating a program that gets user input and puts it into a struct called Student before returning back to a main menu. From the main menu you can add another student. This works but when I try to print all of the Students data that has been added to the array of structs it only prints the last Student that was added.
Here is the struct:
struct Student
{
char* firstName;
int age, cuid;
float GPA;
};
This is how I'm allocating space for the array in main:
struct Student* studentArray = malloc(*recordMaxPtr * sizeof(struct Student));
Here are the functions that capture user input and print the array:
// Gets info and adds student to the array of structs
void addStudents(struct Student* s, int *numStudents)
{
// Allocate memory for the first name
char *firstName = (char*) malloc(25*sizeof(char));
// Gets the name of student
printf("What is the name of the student you'd like to add? ");
gets(s->firstName);
printf("\n"); // blank line
// Gets age of student
printf("How old is the student? ");
scanf("%d", &s->age);
printf("\n"); // blank line
// Gets CUID
printf("What is his / her CUID number? ");
scanf("%d", &s->cuid);
printf("\n"); // blank line
// Gets GPA
printf("What is the student's GPA? ");
scanf("%f", &s->GPA);
printf("\n"); // blank line
}
// Sorts the array and then lists all of the saved students
void printStudents(struct Student* s, int *numStudents)
{
//bsort();
for (int i = 0; i < *numStudents; i++)
{
printf("Student #%d\n", i + 1);
printf(" Student's Name: %s\n", s[i].firstName);
printf(" Age: %d\n", s[i].age);
printf(" CUID: %d\n", s[i].cuid);
printf(" GPA: %2f\n", s[i].GPA);
}
}
I thought about trying to use a for loop to gather all of the student's data that I need at once but this is a school project and I'm not allowed to do it that way. I've been trying to figure this out for a while and I've sort of hit a brick wall on what to do. My best guess is that data is getting overwritten every time you enter a new student to the array but I'm not sure how to fix that.

You allocated a memory for the first name, but didn't save that anywhere. You have to store the address to the structure.
// Allocate memory for the first name
char *firstName = (char*) malloc(25*sizeof(char));
s->firstName = firstName; // add this
Also here are some more things to do for better code:
Check if malloc() succeeded
Check if scanf() succeeded
Replace gets() with fgets() and removal of newline character (gets() has unavoidable risk of buffer overrun and removed from new C specification)
Remove the cast of result of malloc() (c - Do I cast the result of malloc? - Stack Overflow)

Related

Trying to assign user input to elements in a struct but keep getting segmentation-fault 11

struct Book {
char *title;
char *authors;
unsigned int year;
unsigned int copies;
};
void book_to_add()
{
struct Book book;
struct Book *ptrbook = (struct Book*) malloc(sizeof(struct Book));
printf("Book you would like to add: \n");
scanf("%[^\n]", book.title);
printf("Author of Book: \n");
scanf("%[^\n]", book.authors);
printf("Year book was published: \n");
scanf("%u", &book.year);
printf("number of copies: \n ");
scanf("%u", &book.copies);
add_book(book);
free(ptrbook);
}
I am quite new to programming and I am unsure what I should do to solve this, I know it might have to do with the pointer elements in the struct.
title and authors are uninitialized pointers, you will need to allocate memory for them (i.e. make them point to some valid memory location) if you want to store anything there, e.g.:
book.authors = malloc(100);
book.authors = malloc(100);
for a buffer able to store 99 character + the null terminator '\0'.
On that note, make sure to use a size delimiter in scanf otherwise there is the chance for buffer overflow, e.g:
scanf(" %99[^\n]", book.title);
scanf(" %99[^\n]", book.authors);
for a buffer of 100 characters.

Using scanf to create new struct object

Hi I'm using to create a simple program that contains a list of Users (based on the struct below), and I'm trying to create new users based on user input, where I ask for each property seperately using scanf. But I'm having trouble with the structs limitations, for example id should be at max 10 chars and nome can have a max of 25 chars. Here's my code for more context:
struct user {
char id[10];
char name[25];
char group;
float score;
};
struct user list[25];
int registered = 0;
void createNewUser() {
struct user *userPtr, newUser;
userPtr = &newUser;
printf("\nId: ");
scanf("%10s", &(*userPtr).id);
printf("\nName: ");
scanf("%25s", &(*userPtr.name);
printf("\nGroup: ");
scanf("%c", &(*userPtr).group);
printf("\nScore: ");
scanf("%f", &(*userPtr).score);
insert(newUser);
printf("%10s\n", list[0].id);
printf("%25s\n", list[0].name);
printf("%c\n", list[0].group);
printf("%.1f\n", list[0].score);
}
void insert(struct user newUser) {
if (registered < 25){
list[registered] = newUser;
registered += 1;
}
}
With the code I presented above, if I type more than 10 chars for the first input, the next 2 are ignored. And my 3rd scanf is always ignored, the one for group. Can anyone here help me out with this?
The problem with scanf is that when it stops converting characters and there are
more in the input buffer (because the user entered more than you anticipated), then scanf will leave those characters there.
Specially the newline character (inputed when the user presses ENTER)
remains in the input buffer, which causes problems to subsequent calls of
scanf that read characters or strings. So in this case you have to "clean" the input buffer,
so that the next scanf does not consume the left overs of the previous scanf calls.
You can use this function after every scanf:
void clean_stdin(void)
{
int c;
while((c = getchar()) != '\n' && c != EOF);
}
Then you can do:
printf("\nId: ");
scanf("%10s", (*userPtr).id); // no need for &, id is char[]
clean_stdin();
printf("\nName: ");
scanf("%25s", (*userPtr).name); // same here
clean_stdin();
printf("\nGroup: ");
scanf("%c", &(*userPtr).group);
clean_stdin();
printf("\nScore: ");
scanf("%f", &(*userPtr).score);
Also note that the way you are if the maximal length of the ID is 10, then the
buffer must be of length 11, because in C you need to terminate the strings with
the '\0'-terminating byte. So change your structure to this:
struct user {
char id[11];
char name[26];
char group;
float score;
};
Also bear in mind, using a pointer like this
struct user *userPtr, newUser;
userPtr = &newUser;
printf("\nId: ");
scanf("%10s", (*userPtr).id);
...
is not necessary, it actually makes the code harder to read. You can do:
void createNewUser() {
struct user newUser;
printf("\nId: ");
scanf("%10s", newUser.id);
clean_stdin();
...
printf("\nScore: ");
scanf("%f", &newUser.score);
...
}

C - garbage characters even with null terminator, structures

So as part of a school assignment I have to create an application to handle the input of various fields using a structure. defined outside of main as following
typedef struct stud {
struct number {
int studNum;
} number;
struct name {
char firstName[NAME_SIZE];
char lastName[NAME_SIZE];
} name;
struct cellNum {
int areaCode;
int prefix;
int suffix;
} cellNum;
struct courses {
struct fall {
char className[NAME_SIZE];
float mark;
} fall;
struct winter {
char className[NAME_SIZE];
float mark;
} winter;
} courses;
} stud;
NAME_SIZE is defined as 30
I access the struct as following
stud studArray[100]; // max 100 students
stud *studPtr;
for (int i = 0; i < studCount; i++) {
studPtr = &studArray[i];
initializeStrings(studPtr[i]);
getNum(studPtr[i]);
getName(studPtr[i]);
getCell(studPtr[i]);
getCourses(studPtr[i]);
}
initializeStrings is where I add the null terminator's
void initializeStrings(stud student) {
student.name.firstName[NAME_SIZE - 1] = '\0';
student.name.lastName[NAME_SIZE -1] = '\0';
student.courses.fall.className[NAME_SIZE -1] = '\0';
student.courses.winter.className[NAME_SIZE -1] = '\0';
}
This is now where the problem occurs: in any of my various functions, when I try and access any of the information I write to my string, I get an output of garbage characters that look like a sideways T, but only if i access the data in a different function.
Example: this is the code to get 2 courses and marks
void getCourses(stud student) {
getchar();
printf("\n%s", "Enter Course name - Fall 2016: ");
fgets(student.courses.fall.className, NAME_SIZE - 1, stdin);
fflush(stdin); // suggested by my prof to fix the issue, did nothing
printf("\n%s%s%s", "Enter mark received for ", student.courses.fall.className, ": ");
scanf("%f", &student.courses.fall.mark);
getchar();
printf("\n%s", "Enter Course name - Winter 2017: ");
fgets(student.courses.winter.className, NAME_SIZE - 1, stdin);
printf("\n%s%s%s", "Enter mark received for ", student.courses.winter.className, ": ");
scanf("%f", &student.courses.winter.mark);
}
If I enter helloWorld as a course name, the system correctly prints
enter mark received for helloWorld
but when I try and print the data from my print function:
void printData(stud student) {
// various other values (none of which work)
printf("\t%s\t%s\t%f\n", "Fall2016", student.courses.fall.className, student.courses.fall.mark);
}
I get garbage data output ( image )
Any help/code criticism welcome
EDIT: Thank you Eddiem, and everyone else, you pointed me in the right direction.
This code doesn't really make sense. First, why not pass stud* pointers to each of the functions you're calling below? Regardless, the way you have it here, you're initializing studPtr to point to a particular element in the studArray array, but then still indexing i into that pointer. You could probably pass studPtr[0], but that's just weird.
Original code:
stud studArray[100]; // max 100 students
stud *studPtr;
for (int i = 0; i < studCount; i++) {
studPtr = &studArray[i];
initializeStrings(studPtr[i]);
getNum(studPtr[i]);
getName(studPtr[i]);
getCell(studPtr[i]);
getCourses(studPtr[i]);
}
I would suggest modifications like the following:
void initializeStrings(stud *student) {
student->name.firstName[NAME_SIZE - 1] = '\0';
student->name.lastName[NAME_SIZE -1] = '\0';
student->courses.fall.className[NAME_SIZE -1] = '\0';
student->courses.winter.className[NAME_SIZE -1] = '\0';
}
These modifications would need to be made to each if the functions that currently take a stud parameter. Then, change your loop initialization to:
for (int i = 0; i < studCount; i++) {
initializeStrings(&studArray[i]);
getNum(&studArray[i]);
getName(&studArray[i]);
getCell(&studArray[i]);
getCourses(&studArray[i]);
}
This is not right:
studPtr = &studArray[i];
initializeStrings(studPtr[i]);
Say you want to access the third element of your array, so i is 3. This gives you an offset to that third entry:
studPtr = &studArray[i];
Now you use an index with that....
initializeStrings(studPtr[i]);
So you're accessing 3 elements further than the third one you're already at; i.e. element 6. You can see you're going to step off the end of the array, or access elements you haven't initialised.
So use either:
studPtr = &studArray[i];
initializeStrings(studPtr);
or
initializeStrings(&studArray[i]);
Those two amount to the same thing, and point at the same place.
The problem is you're not passing by reference (aka using pointers). If you don't use pointers then functions like getCourses modify temporary data instead of the data directly. Thus, the junk values in the original data are never removed.
Change
void getCourses(stud student) { // no pointer = copy data to temporary memory, original data is unmodified in this function.
getchar();
printf("\n%s", "Enter Course name - Fall 2016: ");
fgets(student.courses.fall.className, NAME_SIZE - 1, stdin);
fflush(stdin); // suggested by my prof to fix the issue, did nothing
printf("\n%s%s%s", "Enter mark received for ", student.courses.fall.className, ": ");
scanf("%f", &student.courses.fall.mark);
getchar();
printf("\n%s", "Enter Course name - Winter 2017: ");
fgets(student.courses.winter.className, NAME_SIZE - 1, stdin);
printf("\n%s%s%s", "Enter mark received for ", student.courses.winter.className, ": ");
scanf("%f", &student.courses.winter.mark);
}
To
void getCourses(stud *student) { // notice the pointer used. Original data is to be modified.
getchar();
printf("\n%s", "Enter Course name - Fall 2016: ");
fgets(student->courses.fall.className, NAME_SIZE - 1, stdin);
fflush(stdin); // suggested by my prof to fix the issue, did nothing
printf("\n%s%s%s", "Enter mark received for ", student.courses.fall.className, ": ");
scanf("%f", &student->courses.fall.mark);
getchar();
printf("\n%s", "Enter Course name - Winter 2017: ");
fgets(student->courses.winter.className, NAME_SIZE - 1, stdin);
printf("\n%s%s%s", "Enter mark received for ", student.courses.winter.className, ": ");
scanf("%f", &student->courses.winter.mark);
}
Further more, you must modify your for loop to handle the pointers. Ie, change getCourses(studPtr[i]); to getCourses(&studPtr[i]);

Runtime Error with Printing Double Pointer Char Array

I am working on creating a system for entering student names and scores into arrays and printing the same information to the screen, unfortunately I keep getting the weird output.
I stepped through my program using a debugger and it showed that everything runs smooth until I get to the function that prints the student's information, there the double pointer char arrays have messed up values.
Here's an image of what I see when I run the program. (http://s28.postimg.org/nv29feawt/Error.png)
Note: Although I am aware that there are better and easier ways of doing this I am required to complete this assignment using dynamically allocated memory and arrays.
int main(void)
{
char **firstNames;
char **lastNames;
float *scores;
int recordsLength;
printf("Please indicate the number of records you want to enter: ");
scanf("%d", &recordsLength);
printf("\n\n");
firstNames = (char **)malloc(recordsLength * sizeof(char *));
lastNames = (char **)malloc(recordsLength * sizeof(char *));
scores = (float *)malloc(recordsLength * sizeof(float));
int i = 0;
while(i < recordsLength)
{
createNewEntry(i, firstNames, lastNames, scores);
i++;
}
printEntry(0, firstNames, lastNames, scores);
free(firstNames);
free(lastNames);
free(scores);
return 0;
}
void clearScreen()
{
#ifdef _WIN32
system("cls");
#elif _unix_
system("clear");
#endif
}
void printEntry(int entryID, char *firstNames[], char *lastNames[], float scores[])
{
clearScreen();
printf("|-------------------------------------------------------------------------|\n");
printf("| Student Entry |\n");
printf("|-------------------------------------------------------------------------|\n|\n");
printf("| First Name: %s Last Name: %s Score: %.1f\n|\n|\n|\n", firstNames[entryID], lastNames[entryID], scores[entryID]);
printf("|-------------------------------------------------------------------------|\n");
printf("| |\n");
printf("|-------------------------------------------------------------------------|\n\n");
}
void createNewEntry(int index, char *firstNames[], char *lastNames[], float scores[])
{
printf("Please input the records of the new student.\n\n\n");
char first[20];
char last[20];
float score = 100.0f;
printf("Please enter the student's first name: ");
scanf("%s", &first);
printf("\n\n");
printf("Please enter the student's last name: ");
scanf("%s", &last);
printf("\n\n");
printf("Please enter the student's score: ");
scanf("%f", &score);
printf("\n\n");
firstNames[index] = (char *)malloc((strlen(first)) * sizeof(char));
firstNames[index] = first;
lastNames[index] = (char *)malloc((strlen(last)) * sizeof(char));
lastNames[index] = last;
printf("first name: %s", firstNames[index]);
printf("last name: %s", lastNames[index]);
scores[index] = score;
}
firstNames[index] = (char *)malloc((strlen(first)) * sizeof(char));
firstNames[index] = first; /* You are missing your allocated memory block and assigning local */
Above lines are incorrect. You can not assign c-strings with assignation = operator. You should use strcpy for that.
You are assigning local array to firstnames, which has no life after the function ends. This invokes an undefined behavior. (You are seeing the strange characters, but it can be even worse).
Should be Re-written as (Similar for last name also)
firstNames[index] = malloc((strlen(first) + 1) * sizeof(char)); /* +1 for \0 */
if(firstNames[index] == NULL) {
/* Malloc failed, error handling */
...
}
/* else */
strcpy(firstNames[index], first); /* Use strcpy to copy contents */
Live example here
Before freeing firstNames and lastNames, you should free all the member of firstNames and lastNames in a loop.
I agree with the answer of Mohit Jain, adding to that you can even use sprintf.

Array for storing names and grades of students in C

I am trying to make a small program in C which will store the first name, last name, and grade of an user inputted number of students. My biggest issue so far is on how to get for the names and grades of each student to print in a new line. With the string operator, I get an error, and with the char operator I only get the first letter and the grade. How would I go about getting the names to fully print? Thanks for all the help in advance.
#include <stdio.h>
#include <stdlib.h>
int main(){
int classsize,i;
printf("Please indicate number of records you want to enter (min 5, max 15):\n");
scanf("%d", &classsize);
char *first, *last;
double *mark;
first=(char*)malloc(classsize*sizeof(char));
last=(char*)malloc(classsize*sizeof(char));
mark=(double*)malloc(classsize*sizeof(double));
printf("Please input records of students (enter a new line after each record), with following format 1. first name 2. last name 3. score.\n");
for (i=0; i<classsize; i++) {
scanf("%s", &first[i]);
scanf("%s", &last[i]);
scanf("%lf", &mark[i]);
}
for (i=0; i<classsize; i++) {
printf("%s, %s has a %lf\n", *(first+i), *(last+i), *(mark+i));
}
}
With
char *first, *last;
You can store only 1 string in the variables as string in C is char *. first is char * and first[i] is char so you have errors related to that. You want first to be char ** and first[i] as char *.
You want
char **first, **last;
And change allocation to (note you don't need to typecast malloc)
//---------------------------------v
first=malloc(classsize*sizeof(char *));
And then in for loop allocate memory for each char * in first and last before reading names in that.
first[i] = malloc(some_size * sizeof(char));
...

Resources