C - garbage characters even with null terminator, structures - c

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]);

Related

User Input to Array of Structs C

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)

s expects argument of type char c but argument 2 has type 'int' warning and bad return

Yes ,I know that this question was already asked for many times ,but none of these helped me to discover the problem (duplicate...yeah). I want to read from input a series of strings into an array and then search from 'First Name'. If the name exist ,I want to display all the data stored in that element of array (I attached the code to undestand easily). When I run it ,I read from keyboard all the data ,but it returns me absolutely nothing.
#include<stdio.h>
typedef struct record {
char name[10],lname[10],phone[10],bday[10];
};
void main() {
struct record rec;
char search;
int i,nr;
printf("\nInput number of records: ");
scanf("%d",&nr);
for (i=0 ; i<nr ;i++) {
printf("First name: ");
scanf("%s",&rec.name[i]);
printf("Last name: ");
scanf("%s",&rec.lname[i]);
printf("Phone: ");
scanf("%s",&rec.phone[i]);
printf("Bday: ");
scanf("%s",&rec.bday[i]);
}
printf("Input the first name for searching: ");
scanf("%s",&search);
for (i=0 ;i<nr;i++) {
if (search == rec.name[i]) {
printf("First name: %s\nLast name: %s\nPhone: %s\nB-day: %s",rec.name[i],rec.lname[i],rec.phone[i],rec.bday[i]);
}
}
}
NOTE: I already replaced
scanf("%s",&rec.name[i]);
with
scanf("%s",rec.name[i]);
but no effect.
I believe there are a lot of problems with your code.
Firstly in this line:
scanf("%s",&search);
You have declared search as only a char, when really you want an array of chars. You also don't need & with search, as an array decays to a pointer to the first element.
It instead should be like this:
char search[10];
scanf("%9s", search); /* %9s to avoid buffer overflow */
You need to make this change to all your other scanf() calls, as this seems to be everywhere in this code.
It also seems that you want to create an array of records(structures), So you might need to make this after getting the value of nr. You can create it like this:
struct record rec[nr]; /* array of nr structures */
This also means calls like this:
rec.name[i]
Don't make sense, as you are iterating over the characters within a name, not over all the records in struct records.
This needs to be instead:
rec[i].name
Secondly, Your using == to compare strings, when you should be using strcmp instead. Using == will only compare the base address of the strings, not the actual contents of strings.
Your line should be this instead:
if (strcmp(search, rec[i].name) == 0) {
If you read the manual page for strcmp(), checking for a return value of 0 means that both strings are equal in comparison.
Lastly, in your first scanf() call:
scanf("%d",&nr);
You should really check the return value of this:
if (scanf("%d", &nr) != 1) {
/* exit program */
}
Note: For reading strings, you should really be using fgets instead. You can try upgrading to this later, but I think it is better to understand these basics first.
Here is working example of what your program should do:
#include <stdio.h>
#include <string.h>
#define STRSIZE 10
typedef struct {
char name[STRSIZE+1]; /* +1 to account for null-btye at the end */
char lname[STRSIZE+1];
char phone[STRSIZE+1];
char bday[STRSIZE+1];
} record;
int main() {
char search[STRSIZE+1];
int i,nr;
printf("\nInput number of records: ");
if (scanf("%d", &nr) != 1) {
printf("Invalid input.\n");
return 1;
}
record rec[nr]; /* array of records */
for (i = 0; i < nr ; i++) {
printf("First name: ");
scanf("%10s", rec[i].name);
printf("Last name: ");
scanf("%10s", rec[i].lname);
printf("Phone: ");
scanf("%10s", rec[i].phone);
printf("Bday: ");
scanf("%10s", rec[i].bday);
}
printf("Input the first name for searching: ");
scanf("%10s", search);
for (i = 0; i < nr; i++) {
if (strcmp(search, rec[i].name) == 0) {
printf("First name: %s\nLast name: %s\nPhone: %s\nB-day: %s\n",rec[i].name,rec[i].lname,rec[i].phone,rec[i].bday);
} else {
printf("Record not found.\n");
}
}
return 0;
}
The numeric input leaves a new line character in the input buffer, which is then picked up by the character input. when numeric input with scanf() skips leading white space, character input does not skip this leading white space.
Use a space before %c and it will help you cause if space is not used then a buffer added with value .so that use space before %c
scanf(" %c",&rec.name[i]);

for loop only printing last item (C)

Hello I am trying to write a C program that basically takes in a structure, stores it in an array, and then prints that structure in this format:
Lastname, Firstname \n
Grade: B
for every item added into the array, now the problem I am having is only the last item that is added is being printed. I know this is happening in the for loop because the code is executing and printing. I am not going to post the entire program because it is a lot of code so I will just put what is important.
void add(char* student_firstname, char* student_lastname, char* student_grade, char* student_level, struct student* list)
{
int i;
for (i = count-1; i < count; i++){
if ((strcmp(list[i].lastName, student_lastname) != 0) && (strcmp(list[i].firstName, student_firstname) != 0)){
strcpy(list[i].firstName, student_firstname);
strcpy(list[i].lastName, student_lastname);
strcpy(list[i].grade, student_grade);
}
else{
printf("This student is already on the list!");
}
//count++;
}
printf("Student added!");
}
The count variable was set to 0 before.
*don't worry about the student_grade
display() function for reference:
void display()
{
int i;
for (i = count-1; i < count; i++){
printf("%s, %s \n", list[i].lastName, list[i].firstName);
printf("Grade: %s \n", list[i].grade);
}
}
Also here is my read() function:
void read()
{
char student_firstName[100];
char student_lastName[100];
char student_grade[30];
char student_level[100];
printf("\nEnter the student's first name:\n");
fgets(student_firstName, sizeof(student_firstName), stdin);
printf("\nEnter the student's last name:\n");
fgets(student_lastName, sizeof(student_lastName), stdin);
printf("\nEnter the student's grade (A+,A,A-,...):\n");
fgets(student_grade, sizeof(student_grade), stdin);
printf("\nEnter the student's education level (f/so/j/s):\n");
fgets(student_level, sizeof(student_level), stdin);
// discard '\n' chars attached to input; NOTE: If you are using GCC, you may need to comment out these 4 lines
student_firstName[strlen(student_firstName) - 1] = '\0';
student_lastName[strlen(student_lastName) - 1] = '\0';
student_grade[strlen(student_grade) - 1] = '\0';
student_level[strlen(student_level) - 1] = '\0';
add(student_firstName, student_lastName, student_grade, student_level, list);
printf("\n"); // newline for formatting
}
Look at the line:
for (i = count-1; i < count; i++)
in both your add and display functions. This goes from count-1 to count, meaning it only ever prints/adds one item at position count-1. You should get rid of the for loop in the add function, increment count, and rewrite the print function to go from 0 to count. This should fix all of the issues you are having.
Your algo for checking for duplicates in add is broken. I need to look more like this, but you should add error checking and protection for bufferoverflow as well.
void add(char* student_firstname, char* student_lastname, char* student_grade, char* student_level, struct student* list)
{
for (int i = 0; i < count; i++){
if ((strcmp(list[i].lastName, student_lastname) == 0) &&
(strcmp(list[i].firstName, student_firstname) == 0)) {
printf("This student is already on the list!");
return;
}
}
strcpy(list[count].firstName, student_firstname);
strcpy(list[count].lastName, student_lastname);
strcpy(list[count].grade, student_grade);
count++;
printf("Student added!");
}
First you need to add protection from that count does not get too big.
Second you need to stop using strcpy and use strncpy instead so as to avoid buffer overflow errors.
Third, you may want to avoid using a linear scan, and figure out how to use a index of some kind, but for a school project that is probably not important.
Similar in your display function, you should adjust the for loop if you expect it to print out all the records, or eliminate the loop if it is only supposed to print the last record.

scanf("%[^\n]") gets skipped

I want to write a little program to learn C; here it is:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int total;
char suffix[3];
struct person {
char id[11];
char name[21];
char sex[7];
int age;
char phone[12];
};
char* number_suffix (int number) {
int mod;
mod = number % 10;
switch (mod) {
case 1:
strcpy(suffix, "st");
break;
case 2:
strcpy(suffix, "nd");
break;
case 3:
strcpy(suffix, "rd");
break;
default:
strcpy(suffix, "th");
break;
}
return suffix;
}
void input_info (struct person info[], int total_people) {
int counter;
for (counter=0; counter<total_people; counter++){
printf("%s%d%s%s\n","Please input the ID(10 digits) of ", (counter+1),
number_suffix(counter), " person: ");
scanf("%s", info[counter].id);
fflush(stdin);
printf("%s%d%s%s\n", "Please input the Name(20 chars) of ", (counter+1),
number_suffix(counter), " person: ");
scanf("%[^\n]", info[counter].name);
fflush(stdin);
printf("%s%d%s%s\n", "Please input the Sex(Male/Female) of ", (counter+1),
number_suffix(counter), " person: ");
scanf("%s", info[counter].sex);
fflush(stdin);
printf("%s%d%s%s\n", "Please input the Age(1~100) of ", (counter+1),
number_suffix(counter), " person: ");
scanf("%d", &info[counter].age);
fflush(stdin);
printf("%s%d%s%s\n", "Please input the Phone of ", (counter+1),
number_suffix(counter), " person: ");
scanf("%s", info[counter].phone);
fflush(stdin);
}
printf("%s\n%s\n%s\n%d\n%s\n", info[counter].id, info[counter].name, info[counter].sex, &info[counter].age, info[counter].phone);
}
int main (void) {
printf("%s\n", "Please input a number that how many people you want to record:");
scanf("%d", &total);
fflush(stdin);
struct person *person_info = malloc(sizeof(struct person)*total);
input_info(person_info, total);
free(person_info);
return 0;
}
I found something weird, when I run it.
Please input a number that how many people you want to record:
1
Please input the ID(10 digits) of 1th person:
A01
Please input the Name(20 chars) of 1th person:
Please input the Sex(Male/Female) of 1th person:
Male
Please input the Age(1~100) of 1th person:
32
Please input the Phone of 1th person:
1224464
[empty line]
[empty line]
[empty line]
1926234464
[empty line]
Is that program skip scanf("%[^\n]", info[counter].name); this line when it run?
Why, and what causes it?
fflush(stdin) is undefined as per the C standard, but it works on some implementations. But it is best to avoid it as it isn't portable and may invoke Undefined Behavior.
To fix the issue, replace all fflush(stdin)s with
int c; /* Declare it once */
while((c = getchar()) != '\n' && c != EOF); /* Discards everything until a newline character or EOF */
Another problem is with
printf("%s\n%s\n%s\n%d\n%s\n", info[counter].id, info[counter].name, info[counter].sex, &info[counter].age, info[counter].phone);
It should be
printf("%s\n%s\n%s\n%d\n%s\n", info[counter].id, info[counter].name, info[counter].sex, info[counter].age, info[counter].phone);
and should be placed inside the for loop. Otherwise, you invoke Undefined Behavior because
You passed an int* for %d which expects an int.
You access invalid memory location beyond the allocated memory segment.
Also, as others have said, pass counter + 1 to number_suffix.
Problem is with your scanf pattern. Use " %[^\n]" instead of "%[^\n]" to not catch \n (After previous data entry)
Pass counter + 1 to number_suffix
How to understand the relation between pointers, struct, malloc, functions parameters?
Read Understanding and Using C Pointers from O'Reilly Media

C input issue

================================================================
typedef struct {
union {
struct {
char fn[5];
char ln[5];
} fullname;
char name[5+5+1];
}
unsigned int age;
unsigned int area_code;
} my_struct;
The above is a struct that I have no control over. I personally am not a fan, but the struct is "legal".
================================================================
My code:
void caller {
my_struct str;
str = (my_struct){}; //initialise
fill(&str);
printf("%s [%s/%s]\n", str.name, str.fullname.fn, str.fullname.ln); // PROBLEM!
}
void fill(my_struct * str) {
//first name
printf("Enter first name: ");
fgets(str.fullname.fn, sizeof(str.fullname.fn), stdin);
if (str.fullname.fn[strlen(str.fullname.fn) - 1] == '\n')
str.fullname.fn[strlen(str.fullname.fn) - 1] = '\0';
//last name
printf("Enter last name: ");
fgets(str.fullname.ln, sizeof(str.fullname.fn), stdin);
if (str.fullname.ln[strlen(str.fullname.ln) - 1] == '\n')
str.fullname.ln[strlen(str.fullname.ln) - 1] = '\0';
sprintf(str.name, "%s %s", str.fullname.fn, str.fullname.ln);
printf("Age: ");
scanf("%ud", &str.age);
getchar();
printf("Area Code: ");
scanf("%ud", &str.area_code);
getchar();
}
================================================================
If the input was:
joe
moe
18
214
The printout at // PROBLEM is:
joe moe [joe moe/oe]
instead of
joe moe [joe/moe]
Any ideas? I cannot, for the life of me, figure out why the values of fn and ln are changing...
The problem is that name and fullname share the memory (because of the union). So
sprintf(str.name, "%s %s", str.fullname.fn, str.fullname.ln);
also writes over fn and ln.
Not a bad question, but I don't really see how to cleanly solve this. The way I'd do it: get rid of the sprintf above, and do it on your own.
void caller
{
fill(&str);
printf("%s %s [%s/%s]\n", str.fullname.fn, str.fullname.ln, str.fullname.fn, str.fullname.ln);
}
the problem is here:
sprintf(str.name, "%s %s", str.fullname.fn, str.fullname.ln);
sprintf can't operate on overlapping memory regions.
What you can do is to NOT put a \0 after the first name, but a space instead of the \n and just print str.name.
Also, initialize the array with ' ' (spaces) it would make inputs for the 1st name that are less than 5 chars.
memset(&str, ' ', sizeof(str));
The name part of the struct is a union, so it's either name or fullname, but not both at the same time. So after setting name the field fullname is invalid. The problem with unions is that you have to provide a mechanism for detecting which part of the union is actually used. I don't see anything here to decide whether name or fullname is used.

Resources