Allocate memory and generate structures in C - c

I'm trying to work through Kernighan's book "C Programming Language" to teach myself C in preparation for a data structures class this spring (C is a required prerequisite), but am stuck on how to deal with multiple structures and how you would store multiples to use later for calculations and output. I wrote some code for structures related to student records with variables for id's and scores which follows. The function names and parameters must stay as is and comments describe what should be done by each function.
So here's what I've tried. I thought of just setting up an array of structures for ten students within the allocate function as follows:
struct student s[10];
However, when I try to return it to main and then pass it to the generate function, I get incompatibility errors. My current efforts are below. However, as you can see, my code fails to store anything but the last set of records (i.e. student.id and student.score) generated. Clearly I'm missing a key component and it's preventing me from being able to generate random unique student id's because I can't check a new id against the previous ones. I also can't move forward in writing functions to run calculations on student scores. Any suggestions would be appreciated. Thanks in advance.
#include <stdio.h>
#include<stdlib.h>
#include<math.h>
#include<conio.h>
#include<assert.h>
struct student{
int id;
int score;
};
struct student* allocate(){
/*Allocate memory for ten students*/
struct student* s = malloc(10 * sizeof(struct student));
assert (s != 0);
/*return the pointer*/
return s;
}
void generate(struct student* students){
/*Generate random ID and scores for ten students, ID being between 1 and 10, scores between 0 and 100*/
int i;
for (i = 0; i < 10; i++) {
students -> id = (rand()%10 + 1);
students -> score = (rand()%(100 - 0 + 1) + 0);
printf("%d, %d\n", (*students).id, (*students).score);
}
}
void deallocate(struct student* stud){
/*Deallocate memory from stud*/
free(stud);
}
int main(){
struct student* stud = NULL;
/*call allocate*/
stud = allocate();
/*call generate*/
generate(stud);
/*call deallocate*/
deallocate(stud);
return 0;
}

Your generate() function only ever accesses the first student structure in your array. You need to use that for loop index in there:
for (i = 0; i < 10; i++)
{
students[i].id = (rand()%10 + 1);
students[i].score = (rand()%(100 - 0 + 1) + 0);
printf("%d, %d\n", students[i].id, students[i].score);
}

Change generate to
void generate(struct student* students){
/*Generate random ID and scores for ten students, ID being between 1 and 10, scores between 0 and 100*/
int i;
for (i = 0; i < 10; i++) {
students[i].id = (rand()%10 + 1);
students[i].score = (rand()%(100 - 0 + 1) + 0);
printf("%d, %d\n", students[i].id, students[i].score);
}
}

Related

How can I arrange the structs in an array of structs in an ascending order?

I am sorry if this sounds confusing, I will try to be as clear as possible. I have an array of structs, where the array stores a struct that I have defined as a Business Card. However, before adding any new business cards into the array, I have to store the structs in ascending order based on the integer value of the Employee ID.
Here is the struct:
typedef struct{
int nameCardID;
char personName[20];
char companyName[20];
} NameCard;
Hence, I tried to use relational operators to compare between the values of the ID and copy it in ascending order to another temporary array I named fakeHolder, before finally copying over to the actual array. However, I can't seem to understand why it is not in order after inputting my data as ID 9, 7, 5.
Here is my helper function:
int addNameCard(NameCard *nc, int *size){
int i = 0;
// Why is this a pointer?
NameCard fakeHolder[10];
char dummy[100];
char *p;
printf("addNameCard():\n");
if(*size == MAX){
printf("The name card holder is full");
// To quit the program
return 0;
}
// Keeps it to Fake Name Card Holder First
printf("Enter nameCardID:\n");
scanf("%d", &fakeHolder->nameCardID);
scanf("%c", &dummy);
printf("Enter personName:\n");
fgets(fakeHolder->personName, 20, stdin);
if(p = strchr(fakeHolder->personName, '\n')){
*p = '\0';
}
printf("Enter companyName:\n");
fgets(fakeHolder->companyName, 20, stdin);
if(p = strchr(fakeHolder->companyName, '\n')){
*p = '\0';
}
// Compare the ID value
for(int j = 0; j < *size; j += 1){
if(fakeHolder->nameCardID == (nc+j)->nameCardID){
printf("The nameCardID has already existed");
}
else if(fakeHolder->nameCardID < (nc+j)->nameCardID){
fakeHolder[(j+1)].nameCardID = (nc+j)->nameCardID;
strcpy(fakeHolder[(j+1)].personName,(nc+j)->personName);
strcpy(fakeHolder[(j+1)].companyName, (nc+j)->companyName);
}
}
*size += 1;
// Transfer to the Actual Name Card Holder
for(int k = 0; k < *size; k += 1){
(nc+k)->nameCardID = fakeHolder[k].nameCardID;
strcpy((nc+k)->personName, fakeHolder[k].personName);
strcpy((nc+k)->companyName, fakeHolder[k].companyName);
}
printf("The name card has been added successfully\n");
return 0;
}
Your current code has several problems, and you can rewrite it to be much more maintainable and easier to work with. For example,
i (in int i = 0;) is not being used
scanf("%c", &dummy); is there, I assume, to remove trailing \n - but a 100-char buffer for a single character to read is... surprising. See scanf() leaves the new line char in the buffer for lots of discussion on different approaches to "trailing stuff after integer".
splitting addNameCard into 2 functions, one to actually request a NameCard and another to insert it into the array, would divide up responsibilities better, and make your program easier to test. Avoid mixing input/output with program logic.
The question you ask can be solved via the standard library qsort function, as follows:
#include <stdlib.h>
typedef struct{
int nameCardID;
char personName[20];
char companyName[20];
} NameCard;
void show(NameCard *nc, int n) {
for (int i=0; i<n; i++, nc++) {
printf("%d,%s,%s\n",
nc->nameCardID, nc->personName, nc->companyName);
}
}
// comparison functions to qsort must return int and receive 2 const void * pointers
// they must then return 0 for equal, or <0 / >0 for lower/greater
int compareCardsById(const void *a, const void *b) {
return ((NameCard *)a)->nameCardID - ((NameCard *)b)->nameCardID;
}
int main() {
NameCard nc[10];
nc[0] = (NameCard){1, "bill", "foo"};
nc[1] = (NameCard){3, "joe", "bar"};
nc[2] = (NameCard){2, "ben", "qux"};
show(nc, 3);
// calling the libraries' sort on the array; see "man qsort" for details
qsort(nc, 3, sizeof(NameCard), compareCardsById);
show(nc, 3);
return 0;
}

Can someone explain to me how can i access a void* item that is inside a void** array, taking in account void** belongs to a struct

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student{
int grade;
int enrollCode;
}student;
typedef struct colVoidStar{
int capacity;
int num_itens_curr;
void **arr;
int current_pos;
}colVoidStar;
colVoidStar *colCreate(int capacity){
if(capacity > 0){
colVoidStar *c = malloc(sizeof(colVoidStar));
if(c != NULL){
c->arr = (void**)malloc(sizeof(void*)*capacity);
if( c->arr != NULL){
c->num_itens_curr = 0;
c->capacity = capacity;
return c;
}
free(c->arr);
}
free(c);
}
return NULL;
}
int colInsert(colVoidStar *c, void *item){
if(c != NULL){
if(c->num_itens_curr < c->capacity){
c->arr[c->num_itens_curr] = (student*)item;
c->num_itens_curr++;
return 1;
}
}
return 0;
}
void *colRemove(colVoidStar *c, void *key, int compar1(void* a, void* b)){
int(*ptrCompar)(void*, void*) = compar1;
student* eleRemoved;
if(c != NULL){
if(c->num_itens_curr > 0){
int i = 0;
for(i; i < c->num_itens_curr; i++){
if(ptrCompar((void*)key, (void*)c->arr[i]) == 0){
eleRemoved = (student*)c->arr[i];
for(int j = i; j < c->num_itens_curr; j++){
c->arr[i] = c->arr[i + 1];
c->arr[i + 1] = 0;
}
return (void*)eleRemoved;
}
return NULL;
}
}
}
return NULL;
}
int compar1(void *a, void*b){
int key;
student *item;
key = *(int*)a;
item = (student*)b;
return (int)(key - item->enrollCode);
}
int main(){
int finishProgram = 0, choose, capacity, returnInsert, removeEnroll;
colVoidStar *c;
student *a, *studentRemoved;
while(finishProgram != 9){
printf("-----------------panel-----------------------\n");
printf("Type: \n");
printf("[1] to create a collection;\n");
printf("[2] to insert a student;\n");
printf("[3] to remove some student of collection;\n");
printf("--------------------------------------------------------\n");
scanf("%d", &choose);
switch(choose){
case 1:
printf("Type the maximum of students the collection will have: \n");
scanf("%d", &capacity);
c = (colVoidStar*)colCreate(capacity);
if(c == NULL){
printf("Error in create collection!\n");
}
break;
case 2:
if(c->num_itens_curr < capacity){
a = (student*)malloc(sizeof(student));
printf("%d student:(type the Grade and the Enroll code, back-to-back)\n", c->num_itens_curr + 1);
scanf("%d %d", &a->grade, &a->enrollCode);
returnInsert = colInsert(c, (void*)a);
if(returnInsert == 1){
for(int i = 0; i < c->num_itens_curr; i++){
printf("The student added has grade = %d e enrollCode = %d \n", (((student*)c->arr[i])->grade), ((student*)c->arr[i])->enrollCode);
}
}else{
printf("the student wasn't added in the collection\n");
}
}else{
printf("it's not possible to add more students to the colletion, since the limit of elements was reached!");
}
break;
case 3:
printf("Type an enrollcode to remove the student attached to it:\n");
scanf("%d", &removeEnroll);
studentRemoved = (student*)colRemove(c, &removeEnroll, compar1(&removeEnroll, c->arr[0]));
if(studentRemoved != NULL)
printf("the student removed has grade = %d and enrollcode %d.", studentRemoved->grade, studentRemoved->enrollCode);
else
printf("the number typed wasn't found");
break;
}
}
return 0;
}
---> As you can realize, what I'm trying to do, at least at this point, is access and remove an item(student* that initially will assume a void* type) of a student's collection(void** arr) using a sort of enrollment code. However, I'm having problems with Segmentation Fault and can't understand why and how can solve them, hence my question up there. Debugging the code I found out the errors lies at: if(ptrCompar((void)key, (void**)*c->arr[i]) == 0) inside of Remove function and return (int)(key - item->matricula) inside of Compar1.
Besides, if you can point me out some articles/documentations/whatever that helps me to understand how to cope with problems like that, I'll appreciate it a lot.
Here are the problems I see in colRemove:
(Not really a problem, just a matter of style) Although the function parameter int compar1(void* a, void* b) is OK, it is more conventional to use the syntax int (*compar1)(void* a, void* b).
(Not really a problem) Having both compar1 and ptrCompar pointing to the same function is redundant. It is probably better to name the parameter ptrCompar to avoid reader's confusion with the compar1 function defined elsewhere in the code.
The function is supposed to be general-purpose and shouldn't be using student* for the eleRemoved variable. Perhaps that was just for debugging? It should be void*.
After the element to be removed has been found, the remaining code is all wrong:
c->num_itens_curr has not been decremented to reduce the number of items.
The code is accessing c->arr[i] and c->arr[i + 1] instead of c->arr[j] and c->arr[j + 1].
c->arr[j + 1] may be accessing beyond the last element because the loop termination condition is off by 1. This may be because c->num_itens_curr was not decremented.
The assignment c->arr[j + 1] = 0; is not really needed because all but the last element will be overwritten on the next iteration, and the value of the old last element does not matter because the number of items should be reduced by 1.
(Not really a problem) There is unnecessary use of type cast operations in the function (e.g. casting void * to void *).
Here is a corrected and maybe improved version of the function (using fewer variables):
void *colRemove(colVoidStar *c, void *key, int (*ptrCompar)(void* a, void* b)){
void* eleRemoved = NULL;
if(c != NULL){
int i;
/* Look for item to be removed. */
for(i = 0; i < c->num_itens_curr; i++){
if(ptrCompar(key, c->arr[i]) == 0){
/* Found it. */
eleRemoved = c->arr[i];
c->num_itens_curr--; /* There is now one less item. */
break;
}
}
/* Close the gap. */
for(; i < c->num_itens_curr; i++){
c->arr[i] = c->arr[i + 1];
}
}
return eleRemoved;
}
In addition, this call of colRemove from main is incorrect:
studentRemoved = (student*)colRemove(c, &removeEnroll, compar1(&removeEnroll, c->arr[0]));
The final argument should be a pointer to the compar1 function, but the code is actually passing the result of a call to the compar1 function which is of type int. It should be changed to this:
studentRemoved = (student*)colRemove(c, &removeEnroll, compar1);
or, removing the unnecessary type cast of the the void* to student*:
studentRemoved = colRemove(c, &removeEnroll, compar1);
The colInsert function is also supposed to be general-purpose so should not use this inappropriate type cast to student*:
c->arr[c->num_itens_curr] = (student*)item;
Perhaps that was also for debugging purposes, but it should just be using item as-is:
c->arr[c->num_itens_curr] = item;
As pointed out by #chux in the comments on the question, the expression key - item->enrollCode in the return statement of compar1 may overflow. I recommend changing it to something like this:
return key < item->enroleCode ? -1 : key > item->enrolCode ? 1 : 0;
or changing it to use this sneaky trick:
return (key > item->enroleCode) - (key < item->enroleCode);

Merge sort in array of structs

I have an array of structs, where each object is a student with a name and a grade, and I'm trying to merge sort this array (I want to sort them by grade in ascending order).
Here is the Student Struct:
struct Student
{
char grade[42];
char city[42];
};
grade is a char because I assign the grade value by getting an input from the user with fgets and sscanf.
I don't think it's necessary to put my whole code of the merge-sort algorithm but I want to share this part which I think might be problematic ?
int firstHalfSize = midElement - firstElement + 1;
int secondHalfSize = lastElement - midElement;
struct Student firstHalfArray[firstHalfSize];
struct Student secondHalfArray[secondHalfSize];
char *p;
char *s;
int index1 = 0;
int index2 = 0;
int mergedArrIndex = firstElement;
while (index1 < firstHalfSize && index2 < secondHalfSize)
{
if (strtol(firstHalfArray[index1].grade, &p, 10) <= strtol(secondHalfArray[index2].grade, &s, 10))
{
arr[mergedArrIndex] = firstHalfArray[index1];
index1++;
}
else
{
arr[mergedArrIndex] = secondHalfArray[index2];
index2++;
}
mergedArrIndex++;
}
the part where I sort the student object by comparing the grade is by converting the char grade into a long with strtol which I think I did good so it might not be a problem.
My problems is that I initialize my array of structs like the following:
struct Student students[5501];
and when I get a new input of the user I just add it into the array like the following:
struct Student aStudent;
int lineCounter = 0;
students[lineCounter] = aStudent;
and increase lineCounter by 1 every time I get a new input. (aStudent is changed when I get a new input)
Here I call the merge-sort function and get weird results:
mergeSort(students, 0, 5501); // 5501 is the size of the array
printArray(students, 5501);
Here is the printArray function created just to see if I get the grade in ascending order
void printArray(struct Student A[], int size)
{
char *p;
int i;
for (i=0; i < size; i++)
printf("%ld", strtol(A[i].grade, &p, 10));
}
but I keep getting that printed:
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000...
and I don't know why.
Also how can I do that If the user adds 3 students object in the array students
I only treat it as like an array of 3 elements and not an array of 5501 elements ?
Thanks! And sorry if it's a little long I really tried to be concise without having a loss of essential informations.
Since the user can input N numbers of students I recommend you to use a Linked List instead and put the node (struct Student) in his right position depending on the grade.
You can learn more about Linked Lists here.

Array of pointers to the struct

I am coding in C a Zoo for a school project. Where there are Areas and Animals within it. We must use dynamic structures. I am trying to do the Areas and I am stuck. I am using a linked list.
Structure
typedef struct area Area, *pArea;
struct area{
char id[10];
int size, nadj;
pArea prox; //for linked list
pArea adj[3]; ///array of pointers to the struct area
};
Filling the list
void fill(pArea p){
printf("ID: ");
scanf(" %10[^\n]", p->id);
printf("Size: ");
scanf(" %d", &p->size);
printf("Nadj: ");
scanf(" %d", &p->nadj);
if(p->nadj == 0)
for(int i = 0; i < p->nadj; i++)
p->adj[i] = NULL;
else
//stuck here. HELP
}
p->prox = NULL;
}
AreaA 500 2 AreaB AreaC
Where AreaA is the id, 500 is the size variable, 2 is the number of Areas (nadj) that will be near the AreaA, following with the areas. Now, my teacher said that the areas near the id Area must be stored in a array of pointers to the struct Area (pArea adj[3], it must be in max 3 Areas) but I don't know how to fill that array while only using the name of the areas as they are on the above example when they are of type struct Area and not an array.
You need to maintain some kind of map from area names to areas. Then
for (int i = 0; i < p->nadj; i++) {
name = read_name();
p->adj[i] = find_area_by_name(name);
}
where find_area_by_name, well, should do what its name suggests. Depending on the amount of areas you need to handle (and level of the class) you may implement it as simple as linear lookup, or as fancy as AVL tree.
BTW,
if(p->nadj == 0)
for(int i = 0; i < p->nadj; i++)
p->adj[i] = NULL;
is effectively a no-op. Since the loop is entered only when p-nadj == 0, it is equivalent to
for(int i = 0; i < 0; i++)

Problems with passing arrays as parameters

I am a novice programmer in C and am running into an issue that is almost painfully simple. I am writing a basic program that creates two arrays, one of student names and one of student ID numbers, then sorts them and prints them in various ways, and finally allows the user to search the arrays by ID number. Here is the code:
#include <stdio.h>
#include <string.h>
#define ARRAY_SIZE 3
#define MAX_NAME_LENGTH 32
int main()
{
// Student info arrays
char NAME[ARRAY_SIZE][MAX_NAME_LENGTH];
int ID[ARRAY_SIZE];
// Array for student IDs, shifted twice to the right
int shiftedID[ARRAY_SIZE];
// Boolean value to keep while loop running and
// the ID search prompt repeating
int loop = 1;
// Counter variable for the for loop
int counter;
// Gets input values for the student info arrays
for (counter = 0; counter < ARRAY_SIZE; counter++)
{
printf("Input student name: ");
scanf("%s", NAME[counter]);
printf("Input student ID: ");
scanf("%d", &ID[counter]);
}
// Sorts the arrays
sort(NAME, ID);
// Prints the arrays
print_array(&NAME, ID);
// Shifts the ID value two bits to the right
shiftright(ID, shiftedID);
print_array(NAME, shiftedID);
// Repeatedely prompts the user for an ID to
// search for
while(loop == 1)
{
search_id(NAME, ID);
}
}
And here are the function definitions:
#define ARRAY_SIZE 3
#define MAX_NAME_LENGTH 32
// Sorts the two arrays by student ID. (Bubble sort)
void sort(char **nameArray, int idArray[])
{
// Counter variables for the for loop
int firstCounter = 0;
int secondCounter = 0;
for(firstCounter = 0; firstCounter < ARRAY_SIZE; firstCounter++)
{
for(secondCounter = 0; secondCounter < ARRAY_SIZE - 1;
secondCounter++)
{
if(idArray[secondCounter] > idArray[secondCounter + 1])
{
// Temporary variables for the sort algorithm
int tempInt = 0;
char tempName[32];
tempInt = idArray[secondCounter + 1];
idArray[secondCounter + 1] = idArray[secondCounter];
idArray[secondCounter] = tempInt;
strcpy(tempName, nameArray[secondCounter + 1]);
strcpy(nameArray[secondCounter + 1],
nameArray[secondCounter]);
strcpy(nameArray[secondCounter], tempName);
}
}
}
}
// Searches the ID array for a user input student
// ID and prints the corresponding student's info.
void search_id(char **nameArray, int idArray[])
{
// A boolean value representing whether or not
// the input ID value was found
int isFound = 0;
// The input ID the user is searching for
int searchID = 0;
printf("Input student ID to search for: ");
scanf("%d", &searchID);
// Counter variable for the for loop
int counter = 0;
while (counter < ARRAY_SIZE && isFound == 0)
{
counter++;
if (idArray[counter] == searchID)
{
// Prints the name associated with the input ID
isFound = 1;
printf("%s", nameArray[counter]);
}
}
// If the input ID is not found, prints a failure message.
if (isFound == 0)
{
printf("ID not found.\n");
}
}
// Prints the name and ID of each student.
void print_array(char **nameArray, int idArray[])
{
// Counter variable for the for loop
int counter = 0;
printf("Student Name & Student ID: \n");
for (counter = 0; counter < ARRAY_SIZE; counter++)
{
printf("%s --- %d\n", nameArray[counter], idArray[counter]);
}
}
// Shifts the ID value to the right by two bits
void shiftright(int idArray[], int shiftedID[])
{
// Counter variable for the for loop
int counter = 0;
for (counter = 0; counter < ARRAY_SIZE; counter++)
{
shiftedID[counter] = idArray[counter] >> 2;
}
}
I am aware that this program is fairly basic in nature, and more than anything it is an exercise to get me more well versed in a language such as C. I've been working on it for some time, and have worked through several problems, but seem to be stuck on three issues:
If the input ID numbers are not input already in order, a segmentation fault results. If the ID numbers are input already in order, the sort function never passes through the if statement, and no problems arise.
When passing the arrays of names/IDs to the print_array function, the IDs are printed just fine, but the names will be printed either entirely blank or as a series of strange characters.
When searching by ID at the end of the program, the ID number that was entered first (so, the number in ID[0]) displays an ID not found message, where all numbers at index 1 or greater will work fine - aside from the corresponding names that should be printed being printed as blank, as mentioned in the second issue.
Any advice that I can get would be greatly appreciated! I find the power behind the fine details needed in C to be both really interesting but also very confusing, intimidatingly so, and that means any help I can get makes a big difference.
The problem is that you are assuming that char [ARRAY_SIZE][MAX_NAME_LENGTH] and char ** are interchangeable
void sort(char **nameArray, int idArray[])
should be
void sort(char nameArray[][MAX_NAME_LENGTH], int idArray[])
or
void sort(char (*nameArray)[MAX_NAME_LENGTH], int idArray[])
in order to use a pointer to an array of MAX_NAME_LENGTH chars, same for your search_id function.
Take a look to question 6.13 of C-FAQ
I would advise you to restructure your program. Rather than storing two independent arrays for names and IDs, you can store one array of structs which contain all the necessary data:
typedef struct student
{
int id;
char name[MAX_NAME_LENGTH];
} student_t;
student_t students[ARRAY_SIZE];
Now you have a single array which can never become "mismatched" by sorting the IDs without the names, etc.
You can sort an array in C using the standard library function qsort():
qsort(students, ARRAY_SIZE, sizeof(student_t), comparator);
This requires you define a comparator, which is fairly simple. One example would be:
int comparator(const void *lhs, const void *rhs)
{
const student_t *s1 = lhs, *s2 = rhs;
return s1->id - s2->id;
}
You can use the same comparator with another standard library function bsearch() to search the array of students after it is sorted:
student_t key = { 42 }; // name doesn't matter, search by ID
student_t* result = bsearch(&key, students, ARRAY_SIZE, sizeof(student_t), comparator);
These standard functions are more efficient than what you had, and require you to write much less code, with fewer chances for mistakes.

Resources