A different question may have been asked about this program, however in this C code I have three functions: one to print records, one to add a record and one to delete a record.
What I don't understand is why the (add) and (delete) do not make changes in the main function, so when I use the print all records function it prints changes, but it doesn't show changes, what is wrong?
Details are in the comments, please feel free to run the code to visualise the problem.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*The program is to store student record (firstname,lastname and score), it should not be more
than 15 records and the names should not be more than 20 chars, array pointersnare being used
instead of arrays, the (add_record) and (delete_record) functions must
do thsi change in the main function, so when you print all records, the changes are shown*/
void print_records(char **firstname,char **lastname,float *score,int *the_size)
{
int i;
printf("Printing All record(s)...\n");
for (i=0;i<*the_size;i++) /*loop to print records of all arrays in correct format*/
{
printf("Firstname : %s, Lastname : %s, Score : %f\n",firstname[i],lastname[i],score[i]);
}
}
void add_new_record (char **firstname,char **lastname,float *score,int the_size)
{
printf("Add new record in the format :\nFirstname Lastname Score\n");
/*the strategy here is to check if all 15 elemts are used, if they are, use realloc
to add one more, if not add the record after the last record*/
if (the_size == 15)
{
firstname=realloc(firstname,16*sizeof(char*));
firstname[15]=malloc((20+1)*sizeof(char));
lastname=realloc(lastname,16*sizeof(char*));
lastname[15]=malloc((20+1)*sizeof(char));
score=realloc(score,16*sizeof(float));
scanf("%s %s %f",firstname[15],lastname[15],&score[15]);
printf("New Record Added Successfully !\n");
printf("Firstname : %s, Lastname : %s, Score : %f\n",firstname[15],lastname[15],score[15]);
}
else if (the_size<15)
{
scanf("%s %s %f",firstname[the_size],lastname[the_size],&score[the_size]);
printf("New Record Added Successfully !\n");
printf("Firstname : %s, Lastname : %s, Score : %f\n",firstname[the_size],lastname[the_size],score[the_size]);
}
}
void delete_record (char **firstname,char **lastname,float *score,int the_size)
{
char *str=malloc(20*sizeof(char)); /*String entered by user must be 20 or less chars*/
int i,ctr=0;
char *temp_first=malloc(20*sizeof(char));/*temp array to swap firstname must be 20 or less chars*/
char *temp_last=malloc(20*sizeof(char)); /*temp array to swap lastname must be 20 or less chars*/
float temp_score;/*ctr is the counter used to check if there are no matchs in the end*/
printf("Enter the lastname of record(s) to delete : ");
scanf("%s",str);
/* the strategy here is to move the element to be deleted to the last index and use
relloc to shrink the size by 1 (-1) */
for (i=0;i< the_size;i++)
{
if (strcmp(str,lastname[i])==0)
{
printf("Deleting Record for %s %s...\n",firstname[i],lastname[i]);
temp_score=score[i];
score[i]=score[the_size-1];
score[the_size-1]=temp_score;
strcpy(temp_first, firstname[i]); /*using strcpy function to swap strings*/
strcpy(firstname[i], firstname[the_size-1]);
strcpy(firstname[the_size-1], temp_first);
strcpy(temp_last, lastname[i]);
strcpy(lastname[i], lastname[the_size-1]);
strcpy(lastname[the_size-1], temp_last);
score=realloc(score,(the_size-1)*sizeof(float));
firstname=realloc(firstname,(the_size-1)*sizeof(char*));
lastname=realloc(lastname,(the_size-1)*sizeof(char*));
ctr++;
the_size--;
}
}
if (!ctr) /*if ctr=0 (no increment), then print,there is no match*/
{
printf ("Sorry, no available record for %s",str);
}
free(temp_first);
free(temp_last);
free(str);
}
void main()
{
char **firstname;
char **lastname;
float *score;
int number_of_records,i,j=-1,ctr=1,row=15,col=20;
/*ctr is to keep track of the student's number (makes it easier to
the user), it starts with (1)*/
firstname=malloc(row*sizeof(char*));
for(i=0;i<row;i++)
{
firstname[i]=malloc((col+1)*sizeof(char));
}
lastname=malloc(row*sizeof(char*));
for(i=0;i<row;i++)
{
lastname[i]=malloc((col+1)*sizeof(char));
}
printf("\nPlease indicate number of records you want to enter (min 2, max 15): ");
scanf("%d",&number_of_records);
score=malloc(row*sizeof(float));
printf("\nPlease input records of students\n(enter a new line after"
"each record), with following format:\nfirst name last name score ");
for (i=0;i<number_of_records;i++)
{
printf("\nEnter record for student %d : ",ctr);
scanf("%s %s %f",firstname[i],lastname[i],&score[i]);
ctr++; /*ctr is to keep track of student number
(makes it easy to the user) */
}
while (j!=0) /*Main menu will keep looping after using a function as long as j is not 0
When the user enters 0 (zero) the loop will stop and therefore the program will terminate*/
{
printf("\nSelect desired function by pressing the corresponding key number\n");
printf("\n********** Main Menu **********\n");
printf("\n>>> Print records (press 1)\n");
printf("\n>>> Add a new Record (press 2 )\n");
printf("\n>>> delete record (press 3)\n");
printf("\n>>> Exit the program (press 0)\n");
scanf("%d",&j); /*getting j from the user (j is used for selection and for the while loop)*/
if (j==1)
{
print_records(firstname,lastname,score,&number_of_records);
}
else if (j==2)
{
add_new_record(firstname,lastname,score,number_of_records);
}
else if (j==3)
{
delete_record(firstname,lastname,score,number_of_records);
}
else if (j==0)
{
printf("Exitting program ...\n");
}
}
}
As other answers have observed, in C, all arguments are passed by value. That means the function gets a copy of the caller's value, therefore changes to that value are not visible to the caller. In other words, given
void f(any_type arg) {
arg = any_value;
}
The caller will never detect any change, regardless of what type any_type is or what value any_value is. Note carefully, however, the difference between that, and this:
void f(any_type *arg) {
*arg = any_value;
}
In that case, it is not the function argument (a pointer) that is being modified, it is the thing pointed to. The argument is a copy of the caller's value, so the two point to the same thing. The caller cannot detect changes to the argument, but after the call, it can detect changes to the thing it points to.
Your code exhibits some issues of this type, some of them responsible for your main problems. Most importantly, these have to do with your record keeping on the number of elements in your list (variable number_of_records in main()). Your addition and deletion functions work more or less ok, except that they cannot communicate the revised list size back to main.
There are additional issues in add_new_record() when there are already 15 records; if you can, I would just disallow that case. If you must support it, then you have multiple things to clean up. Some of them to do with pass-by-value issues, others to do with what your code should do when the list initially contains 16 or more records.
Update:
Since you're having so much trouble working this out, here's a revised version of delete_record(). It implements a lot more than the minimal changes required to get the desired output upon record deletion, as there was in fact a goodly number of other issues that I might as well call out as long as I'm going to the trouble. See new and modified comments.
/*
* Can realloc() *firstname, *lastname, and *score and return the updated
* values to the caller. Most importantly, can update *the_size and have
* the caller see the result.
*/
void delete_record (char ***firstname, char ***lastname, float **score, int *the_size)
{
int i;
int initial_size = *the_size;
char str[21]; /* no need to malloc a fixed-length local array */
printf("Enter the lastname of record(s) to delete : ");
fflush(stdout); /* The prompt might not appear if you don't flush */
/*
* Note the field width in the format below. Without it, a user can
* easily cause a buffer overflow.
*/
scanf("%20s", str);
/*
* The strategy for each element to delete (there may be more than one)
* is to free the element's name components (else their allocated memory
* leaks), copy the last (at that time) element's components into
* place (for the name components, just the pointers), and later
* realloc to shrink the overall size to exactly fit the remaining
* elements (once we know how many that is).
*/
for (i = 0; i < *the_size; )
{
if (strcmp(str, (*lastname)[i]) == 0)
{
printf("Deleting Record for %s %s...\n", (*firstname)[i], (*lastname)[i]);
free((*firstname)[i]);
free((*lastname)[i]);
(*firstname)[i] = (*firstname)[*the_size - 1];
(*lastname)[i] = (*lastname)[*the_size - 1];
(*score)[i] = (*score)[*the_size - 1];
*the_size -= 1; /* not the same as *the_size-- */
/* don't increment i, else we miss testing the new i'th element */
} else {
i += 1;
}
}
if (*the_size != initial_size)
{
void *temp;
/*
* Always check the return value of realloc(), even when you're
* shrinking the allocation. Usually, though, you'd want at least
* some kind of diagnostic in the event of failure.
*/
temp = realloc(*firstname, sizeof(char *) * (*the_size));
if (temp)
{
*firstname = temp;
}
temp = realloc(*lastname, sizeof(char *) * (*the_size));
if (temp)
{
*lastname = temp;
}
temp = realloc(*score, sizeof(float) * (*the_size));
if (temp)
{
*score = temp;
}
}
else /* there is no match */
{
printf ("Sorry, no available record for %s",str);
}
}
Your main() would call that like so:
delete_record(&firstname, &lastname, &score, &number_of_records);
Similar changes are needed to add_record(), though you do have the separate issue I already called out there with increasing the number of entries past 16.
Additionally, you're making extra work for yourself by using separate arrays of first name, last name, and score. It would be much easier to define a struct encompassing all three, and use just one dynamic array whose elements are instances of that struct.
First, you're declaring the arguments of add_ as pointers (presumably because you want the function to change the value of the variables whose addresses you pass):
add_new_record (char **firstname, char **lastname, float *score, int the_size) {
But then you simply assign the locals, not the things they point to:
firstname = realloc(firstname,16*sizeof(char*));
score = realloc(score,16*sizeof(float));
If you want to change the variables to which these point, you have to
dereference them for assignment:
*firstname = malloc(16);
*score = 1.0;
Now the original firstname pointer points to a valid chunk of memory (presumably you'll want to strcpy() some actual name there), and the original float variable to which score points is now 1.0.
There are other problems, but that's the primary reason this function doesn't change what you think it should, because you didn't tell it to.
Related
void inputData(){
printf("Enter contact name : "); gets(temp.name);
fflush(stdin);
printf("Enter contact email : "); gets(temp.email);
fflush(stdin);
printf("Enter contact phone number : "); gets(temp.phoneNum);
fflush(stdin);
int index = hash(temp.phoneNum, sizeof(table1.listContact)/sizeof(table1.listContact[0]));
if (checkDuplicate(index)){
puts("Number is used");
return;
}
if(strcmp(table1.listContact[index].phoneNum, "foo")){
index = linearRehash(index, sizeof(table1.listContact)/sizeof(table1.listContact[0]));
if (index == -1){
puts("Memory Full);
return;
}
if (checkDuplicate(index)){
puts("Number is used");
return;
}
}
strcpy(table1.listContact[index].name, temp.name);
strcpy(table1.listContact[index].email, temp.email);
strcpy(table1.listContact[index].phoneNum, temp.phoneNum);
}
I'm using hash tables to create a contact list, and to prevent entering the same phone number i think i need to check the data (using checkDuplicate()) in the index twice (once after the first hash, second after the rehashing), is there a way so i only need to check it once?
int checkDuplicate(int index){
if (!strcmp(table1.listContact[index].phoneNum, temp.phoneNum)){
return 1;
}
return 0;
}
int linearRehash(int hash, int m){
int i = 0;
while (strcmp(table1.listContact[hash].phoneNum, "foo")){
hash = (hash+1)%m;
if (checkDuplicate(hash)){
return hash;
}//If duplicate found, return index with the same data
if (i == m){
return -1;
}//If no space in hashtable return -1
i++;
}
return hash;
}
I think it's the checkDuplicate inside the linearRehash function that you want to avoid doubling up on, right?
First of all, I think linearRehash could be modified into something like linearHash, which would do the initial hashing and the rehashing. Then all the duplicate checking could be done inside linearHash.
You'd still need a way for linearHash to return three different types of values: "memory full" (currently -1), "duplicate found" (currently just returns the index) and "available space found" (also currently just returns the index). Of course there are ways that you can do this (e.g. passing data through pointer parameters), but that seems unnecessarily complicated.
Instead I would suggest changing your "empty space" representation from "foo" to either a NULL pointer (if phoneNum is a pointer) or an empty string (if phoneNum is a fixed length char array). Then you can check if you're looking at an empty slot without doing a strcmp (either phoneNum == NULL or phoneNum[0] == '\0'). Then just have linearHash return the index of the slot found (either a duplicate or an empty slot, or -1 if memory full), and then have inputData check the returned index to see if it points to an empty slot (in which case insert the details into the slot) or a non-empty slot (in which case print "Number is used").
Of course, you're still doing that extra "empty slot" check on the returned value, but it doesn't matter so much.
How can I add different types in a table? Firstly I have to create a function in order to add the food that I ate (char), the calories (int) and the hour that I ate it (float) in a table with maximum size [100][4].
The only knowledge that I have and I can use for this project for my university is pointers and tables, NOT structures (which is the solution I was also thinking)
I've tried many things and the only thing that I did is to fill only the first column with the name of the food.
for (j=0;j<4;j++){
if (j==0){
printf ("Add your food:\n");
//char
scanf("%s",&table[n][j]);
}else if (j==1){
printf ("Add calories:\n");
//int
scanf("%d",&table[n][j]);
}else if (j==2){
printf ("Add the time you ate:\n");
//float
scanf("%.2f",&table[n][j]);
}else if (j==3){
printf ("Kati\n");
}
}
I expected my code to show all the data I filled but of course that doesn't work. So is there any solution to add different types in a table?
add different types in a table? ... pointers and tables, NOT structures ..
... as char *table[100][4] ...
Save all data as strings. Convert the type/value into a string with enough information to reconstruct the type/value later.
#include <float.h>
#include <stdlib.h>
void table_add(char *table[100][4], size_t index, const char *food, int calories, float hour) {
table[index][0] = strdup(food);
char buf[42]; // large enough for a 128 bit `int`
sprintf(buf, "%d", index);
table[index][1] = strdup(buf);
sprintf(buf, "%.*e", FLT_DECIMAL_DIG - 1, index);
table[index][2] = strdup(buf);
table[index][3] = NULL; // Unclear what OP needs a 4th element for
}
Usage
#define FOOD_SIZE 50
char *table[100][4] = { 0 };
for (index = 0; index < 100; index++) {
char food[FOOD_SIZE];
printf ("Add your food:\n");
scanf("%49s",food);
int calories
printf ("Add calories:\n");
scanf("%d",&calories);
float hour;
printf ("Add the time you ate:\n"); // Unclear why OP is using float for `time`
scanf("%f", &hour);
printf ("Kati\n");
table_add(table, index, food, calories, hour);
}
// now use the data somehow
index = ...
food = table[index][0];
calories = atoi(table[index][1]);
hour = atof(table[index][2]);
printf("Food:%s Calories:%d Time:%.2f\n", food, calories, hour);
// When done, free all allocations
for (index = 0; index < 100; index++) {
for (j = 0; j < 4; j++) {
free(table[index][j]);
}
}
For details on FLT_DECIMAL_DIG - 1 in sprintf(buf, "%.*e", FLT_DECIMAL_DIG - 1, index); see Printf width specifier to maintain precision of floating-point value.
Disclaimer This is not a general purpose solution, in nearly all cases there are better ways of doing such an exercise. However this is what your assignment restrictions call for
In C a pointer is allowed to alias any other type of pointer (though there are special restrictions when it comes to dereferencing such pointers), so you have to type pun your other types into your array (which is generally risky as you give up type safety). Modifying your code sample this would look like this (I have removed the loop and branching as I found them to hinder readability):
printf ("Add your food:\n");
// 50 is just to showcase, replace with actual value in your code
table[n][0] = malloc(50 * sizeof(char));
scanf("%s",table[n][0]);
printf ("Add calories:\n");
table[n][1] = malloc(sizeof(int));
scanf("%d",(int*)table[n][1]);
printf ("Add the time you ate:\n");
table[n][2] = malloc(sizeof(float));
scanf("%f",(float*)table[n][2]);
printf ("Kati\n");
Also make note of the changes I made to the scanf lines to make sure that the pointers passed in are of the actual correct types. Since you malloc all elements of the array you also need to remember to free them all at the end of your program to avoid memory leaks.
EDIT As commented by OP, table is defined as char* table[100][4]
I am trying to make a program that will arrange my structures by people ids.
I used long double because IDs are 20 digits long.
For example if I introduce 3 persons:
1.Alex Alex / id = 219(...)
2.John John / id = 200(...)
3.Robert Robert / id = 199(...)
I want my program to rearrange so Robert comes first, John second and Alex third.
I am having a problem on "for" structure - I don't exactly know how to swap two structures since I have chars and ints combined.
typedef struct
{
char name;
char prename;
long double id;
int j;
} PERSON;
int main()
{
int n,i,j;
printf ("How many people = ");
scanf("%d", &n);
PERSON v[n];
for(i=1;i<=n;i++)
{
printf("For person number nr. %d\n", i);
printf("name = ");
scanf("%s", &v[i].name);
printf("Prename = ");
scanf("%s", &v[i].prename);
printf("id = ");
scanf("%d", &v[i].id);
}
for(int i=0; i<n; i++)
{
for(int j=0; j<n-1; j++)
{
if( v[i].id > v[j+1].id )
{
int temp = v[j].id;
char temp2[100];
char temp3[100];
strcpy(v[j].prename,temp3);
strcpy(v[j].name,temp2);
v[j].id = v[j+1].id;
v[j+1].id = temp;
}
}
}
return;
}
Ok, because there is so many things you've done here that are not obvious to a new C developer, I want to point them out to help you learn:
typedef struct
{
// These need to be arrays if they are to be strings
// Preferably using a constant for size or dynamic
// if you want them to be variable sized to match input.
char name;
char prename;
// Do not use floating point numbers to represent integer values.
// IRL, you'd use a library, but here, you may want to use an array of
// some sort of integer type instead.
long double id;
// This is a really poor name for a struct variable and probably shouldn't be here.
int j;
} PERSON;
int main()
{
int n,i,j;
printf ("How many people = ");
// Dropping raw output from scanf into a memory allocation is crazy dangerous.
// At least error check the results to be sure it is meaningful.
scanf("%d", &n);
// This is a variable length array and often is not supported directly.
// You probably want to use a malloc()/free() pair to handle this.
// (Update: VLA is now part of newer standards, but are not safe because they cannot fail gracefully on out of memory.)
PERSON v[n];
// Anytime in C I see an array start at 1 and use <= for condition, I get very nervous because
// it tends to lead to indexing errors.
for(i=1;i<=n;i++)
{
printf("For person number nr. %d\n", i);
printf("name = ");
// Oops - and this is why. You just skipped the first entry at 0
// and will overwrite memory on the last loop.
// Also, scanf into a string without a length can blow up memory...
scanf("%s", &v[i].name);
printf("Prename = ");
// Ditto
scanf("%s", &v[i].prename);
printf("id = ");
// Ditto - worse because you've crossed your types - %d doesn't go into a long double.
scanf("%d", &v[i].id);
}
// Should be its own function to make it easier to swap later to a better sort.
for(int i=0; i<n; i++)
{
// Bubble sort usually wants j=i here.
for(int j=0; j<n-1; j++)
{
if( v[i].id > v[j+1].id )
{
// Make a swap function here. Makes it clearer what you want to do.
int temp = v[j].id;
// What is 100? How do you know that is enough?
// These are called magic numbers and lead to code death.
char temp2[100];
char temp3[100];
// Ah, strcpy - 3 things wrong here.
// 1 - You have the parameters backwards - you are copying temp3 to your struct.
// 2 - You have no way to know if the destination will fit the source because it copies until it finds a '\0' - very dangerous.
// 3 - Because your parameters are backwards and temp123 is not initialized, this very well could copy forever.
// strncpy (also crazy dangerous) at the least should be used and consider using better means like strlcpy() and such.
strcpy(v[j].prename,temp3);
strcpy(v[j].name,temp2);
v[j].id = v[j+1].id;
v[j+1].id = temp;
// You kinda forgot to swap the strings, but the program is already dead so no worries.
}
}
}
// Please enable compiler warnings - you must return a value here.
return;
}
In seriousness, I'm sure I missed a few other things, but this is good enough for a free internet code review and learning session. :)
Info on strcpy() and strncpy()
Why is strncpy insecure?
Info on scanf() safety
How to prevent scanf causing a buffer overflow in C?
Info on Variable Length Array safety:
Is it safe to use variable-length arrays?
The main problem with your code is here:
typedef struct
char name; <--- just a char! no room for a string
char prename; <--- just a char! no room for a string
long double id;
int j;
} PERSON;
You need to make these arrays so that they can hold the names.
Like:
#define MAX_NAME_LEN 100
typedef struct {
char name[MAX_NAME_LEN];
char prename[MAX_NAME_LEN];
long double id;
int j;
} PERSON;
Besides that, you should always check the value returned by scanf and never do scanf("%s", &v[i].prename); as it may lead to buffer overflow. Instead do scanf("%99s", v[i].prename); but even better use fgets
And for sorting... just use qsort - see http://man7.org/linux/man-pages/man3/qsort.3.html
BTW: A long double can't be scan'ed using scanf("%d", &v[i].id); - %d is for integers. That said, I doubt you want to use long double. You probably want some interger type - maybe long long unsigned or uint64_t
Disclaimer: This is my first question on StackOverflow and I'm a novice programmer, so my apologies ahead of time if you are appalled by my code or if I don't post my question appropriately.
Anyways, I'm working on a gradebook with dynamically allocated structures. I've divided the gradebook into three structues, a student structure (student name, student id), a course structure (course name, course id), and an enroll structure(student id, course id, grade).
Problem: I'm able to input as many grade as I need without errors for the first student. When I try to input a grade for the second student my program core dumps. I've checked all the variables involved to see if they are passed to my function appropriately and they are. The following is my Enroll structure and my add grade function.
typedef struct {
int Student_ID;
int Course_ID;
int *Grade;
int GradeCount;
} Enroll_Database;
Function...
void addGrade(Enroll_Database *Enroll)
{
int i = 0, j = 0, b, Course_Num, Student_Num, Grade;
printf("Enter Course Number: ");
scanf("%d", &Course_Num);
printf("Enter Student ID: ");
scanf("%d", &Student_Num);
/* For loop that traverses through the Enroll array until until it encounters
nothing in the Course ID */
for(i = 0; Enroll[i].Course_ID != 0; i++)
{
/* if the Student Number and the Course Number are equal to their
appropriate Enroll element, then ask user to input Grade */
if(Enroll[i].Student_ID == Student_Num && Enroll[i].Course_ID == Course_Num)
{
printf("Enter Grade: ");
scanf("%d", &Grade);
if(Enroll[i].GradeCount == 0)
{
Enroll->Grade = (int *) malloc(sizeof(int));
Enroll[i].Grade[Enroll[i].GradeCount] = Grade; //core dumps
Enroll[i].GradeCount++;
}
else
{
Enroll->Grade = (int *) realloc(Enroll->Grade, sizeof(int));
Enroll[i].Grade[Enroll[i].GradeCount] = Grade; //core dumps
Enroll[i].GradeCount++;
}
}
}
}
I've ran multiple checks and the core dump occurs after I malloc/realloc and assign user input to the grade value in the enroll structure.
I'd greatly appreciate any help, and I'm sorry again if my code is unreadable or I formatted wrong.
Thanks!
This only allocates space for one element,. and also it reallocates the wrong pointer:
Enroll->Grade = (int *) realloc(Enroll->Grade, sizeof(int));
It could be fixed like this:
Enroll[i].Grade = realloc(Enroll[i].Grade, sizeof(int) * (Enroll[i].GradeCount + 1));
Remembering that X->Y, (*X).Y, and X[0].Y all mean the same thing: your original version actually reallocated Enroll[0].Grade, instead of Enroll[i].Grade.
(The rest of this answer is some possible style improvement suggestions:)
To avoid this sort of error, I'd personally write just after the scanf:
Enroll_Database *found = &Enroll[i];
and then use found-> everywhere instead of Enroll[i]. Alternatively I'd consider having a separate function to actually add the grade (which is called once the database entry has been found).
Now, if you initialize Enroll[i].Grade to NULL when setting up the database, you actually wouldn't need this if...else statement. Since the behaviour of realloc(NULL, X) is the same as malloc(X), the same code would handle both situations.
Note that in C you should not cast the value returned by malloc and friends.
Another thing to bear in mind is that the style X = realloc(X, ... does not allow you to recover from allocation failure. To write robust code you need to take some sensible action when a malloc-family function returns NULL; this could be as simple as printing a message and calling exit.
From my last post I'm dealing with my code block by block to make sure it's all working and there's something very strange going on.
If you look at my code, when it comes to initialising the structure with the unique student IDs it will happily print them after each one has been initialised inside the for loop (which suggests to me it's obviously done it) but, however, when I want to print them again outside the for loop in another for loop as a "double-check" it goes horribly wrong, presumably they're not in there at all?
If you traverse the code to the bottom you'll see my comments.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define ROWS 80
#define SIZE 100
#define STUDENTS 20
int string_compare(void const *x, void const *y)
{
return strcmp(*(char**)x, *(char**)y);
}
struct student
{
char student_ID[SIZE];
};
int main(void)
{
FILE* input;
int i,j,data_items;
int records=0;
char buffer_IDs[ROWS][SIZE];
char buffer_subjects[ROWS][SIZE];
int marks[ROWS];
char *string_ptrs[ROWS];
struct student db[STUDENTS];
if((input=fopen("C:\\marks\\marks.txt", "r"))==NULL)
perror("File open failed!");
else
{
while ( ( data_items=fscanf(input, "%s %s %d", buffer_IDs[records], buffer_subjects[records], &marks[records])) == 3) {
printf("%s %s %d\n", buffer_IDs[records], buffer_subjects[records], marks[records]);
string_ptrs[records]=buffer_IDs[records];
records++;
if ( records > ROWS) {
break;
}
}
}
qsort(string_ptrs, records, sizeof(char*), string_compare);
for(i=0;i<records;i=i+4)
{
j=0;
strcpy(db[j].student_ID,string_ptrs[i]);
printf("%s\n",db[j].student_ID); /*Happily prints the unique IDs contained in the structure*/
j++;
}
for(i=0;i<STUDENTS;i++)
printf("%s\n",db[i].student_ID); /*Does NOT print them outside the for loop which initialises the structure. */
return 0;
}
Looks like your first for loop is always printing only the first student id. at the beginning of the loop, you set j as 0, and at the end you increment it. But, after each step of the loop, the j becomes 0 again.
So, only the first student id are being "initialized", and the next elements are all uninitialized.
Why is your "initialization" cycle jumps over 4 string pointers at each iteration
for(i=0;i<records;i=i+4)
...
?
This cycle will initialize only records / 4 elements in the db array. I.e. the final value of j will tell you how many elements in db have meaningful student_ID values. (And which will be only 1, since as Alef noted in his answer, you reset the value of j on each iteration of "initialization" cycle).
Later you print STUDENTS elements. STUDENTS and the final value of j are unrelated. What if STUDENTS is greater than j? Expectedly, you will print complete garbage from uninitialized elements of db.