I have an assignment in C that basically asks for some sort of interface/database for a supposed animal shelter, and we were given these 2 structures:
typedef struct age
{
int years, months;
}age;
typedef struct pet
{
int id;
char* sex;
char* breed;
age* pet_age;
}pet;
The interface has to have several functions, like adding a new pet (in our case dogs specifically), removing based on ID, searching for all pets of the same breed and changing the name of a breed entirely, and it all has to be done dynamically using a pet* array as well as the malloc and realloc functions. The entries have to be written in a file and also read from it, but that's something I'll figure out after I figure out how to handle the functions regarding my dynamic array first.
To get to the point, I am having trouble understanding how to scan/reference an instance's pet_age. I've tried it a myriad different ways but I don't understand what's wrong, really. The program crashes/exits after I scan the months element.
Here is the insertion function I have implemented thus far. While not correct, the main source file still compiles.
void addPet(pet *p){
if(i=1){ //First time activation check.
p=malloc(k*sizeof(p));
if(!p){
printf("\nUnable to allocate memory...");
exit(0);
}
}
p[i].sex = malloc(sizeof(char)*1);
p[i].breed = malloc(sizeof(char)*20);
p[i].pet_age =malloc(sizeof(int)*2);
p[i].id = i; //Autogenerated ID
printf("\n%d\n", p[i].id);
printf("Insert pet's breed:"); //Scan breed
scanf("%s", p[i].breed);
printf("Insert pet's sex:"); //Scan sex
scanf("%s", p[i].sex);
printf("Insert pet's age in years:"); //Scan years
scanf("%d", p[i].pet_age->years);
printf("\n%d\n", p[i].pet_age->years);
printf("Insert pet's age in months:"); //Scan months
scanf("%d", p[i].pet_age->months);
printf("\n%d\n", p[i].pet_age->months);
i++; //Incrementing counter
if(i==k){
k+=10;
p=realloc(p, k*sizeof *p); //Size check
}
}
For now there is a basic initialization in the event that this is the first insertion. Then I allocate memory for each element of the structure (to the best of my understanding), and scan every element with a scanf (I pasted some printf checks to see what was actually scanned). Then at the end I increment the i counter, followed by a size check to allocate 10 more places for the array in the event that i==k.
For the sake of continuity, here is my main function as well (basic menu and all):
int i=1; //Counter
int k=10; //Default max entries
int main(int argc, char *argv[]) {
FILE *fp;
int choice;
pet *petarray;
//Menu that lists every option.
while(1){ //Endless loop that ends only if you choose to exit through the 5th option.
printf("\n\n Menu:");
printf("\n=========");
printf("\n1. Insert information for a new pet.");
printf("\n2. Delete a pet record based on pet's ID.");
printf("\n3. Search a pet record based on pet's breed.");
printf("\n4. Update pet's breed name.");
printf("\n5. Exit.\n\n");
scanf("%d", &choice);
switch(choice)
{
case 1:
addPet(petarray);
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
printf("Exiting program...");
exit(0);
}
}
return 0;
}
Apologies if this seems amateur, I'm quite the rookie and still learning. Thanks in advance.
It compiles, but don't you get a long list of warnings? If you don't, you should turn on warnings.
But let's have a look.
void addPet(pet *p)
{
if (i = 1)
You are not comparing (global!) i to 1 here. You are assigning to it. This if statement can only take the true path because of that. When you assign to i, the result of the assignment is the value you assign, so you are testing if (1) here. You want to take this path only if i is 1, I guess, so you should use if (i == 1).
{ //First time activation check.
p = malloc(k * sizeof(p));
Well, it is every time, but we have fixed that now. What do you want p to be, here? An array of k pets? That is not what you are allocating memory for. You are allocating space for k times sizeof(p) and since p is a pet *p, that means you are allocating space for k pointers to pets. Not pets. That, of course, is a problem since p is a pointer to pet and not a pet **. You have most likely allocated too little memory here.
This, unfortunately, is usually not something you will get a warning about. You can give malloc() any size, and it will give you that amount of memory. If you asked for the wrong amount, you get the wrong amount. I think you wanted malloc(k * sizeof *p) here. That allocates space for k of the kind of objects p points to, and that means you can use p as an array of k of that type. You do it the right way when you realloc() later, so this is probably just a quick mistake, but it can easily destroy everything at runtime.
p[i].sex = malloc(sizeof(char) * 1);
p[i].breed = malloc(sizeof(char) * 20);
Two issues here. First, are you sure that p has an entry i? If you fixed the allocation above, then the p you allocated the first time has room for k pets, but this could be any p we have called the function with, so we don't know about this one at all. There is absolutely no guarantee that it is valid to access p[i]. Your reliance on the two global variables will generally make this very dodgy; you simply cannot assume that the function is called with the specific pointer you allocated memory for a bit earlier.
Second, for the string allocation, there are a few red flags as well. sizeof(char) is always 1, so you don't need it. It isn't wrong, really, it just looks odd. And are you absolutely sure that you are allocating enough memory? For p[i].sex I find it highly unlikely. You are getting space for exactly one char. If you only want one char, then that is fine, but you you should probably declare sex a char instead of a char *. If you plan to put a string in p[i].sex, then it will have to be the empty string and nothing longer, because you have only room for the '\0' terminal in a buffer of length 1.
With
p[i].pet_age = malloc(sizeof(int) * 2);
it might technically work, but I don't think the standard guarantees it. You are allocating space for a struct age, and that struct holds two int. They will align the right way, so there shouldn't be any padding, and therefore it should work, but it is flaky as hell.
If you want to allocate space for a struct, then do that. malloc(sizeof(struct age)) gets the job done. Even better, gets the type from the variable you are allocating space for:
p[i].pet_age = malloc(sizeof *(p[i].pet_age));
If p[i].pet_age is a struct age *, then *(p[i].pet_age) is a struct age, and it is the size of that we want.
Then we read in the data.
printf("Insert pet's breed:"); //Scan breed
scanf("%s", p[i].breed);
Here we can have a buffer overflow.
printf("Insert pet's sex:"); //Scan sex
scanf("%s", p[i].sex);
Here we are guaranteed one, because we need to write the terminal zero into sex after we put the data there.
printf("Insert pet's age in years:"); //Scan years
scanf("%d", p[i].pet_age->years);
printf("\n%d\n", p[i].pet_age->years);
printf("Insert pet's age in months:"); //Scan months
scanf("%d", p[i].pet_age->months);
printf("\n%d\n", p[i].pet_age->months);
Since scanf needs to store the data it reads somewhere, it needs a pointer to where it should put it. You are providing integers. (Your compiler definitely should have warned you here). You should use &p[i].pet_age->years to store an integer in p[i].pet_age->years, and the same for months.
Then we get to what I think is probably the worst error in the code.
if (i == k)
{
k += 10;
p = realloc(p, k * sizeof *p); //Size check
}
I'm not going to comment on the global variables again, but rather the local variable. This realloc potentially destroys the memory that p pointed at. I don't care that it can return NULL and you don't check; I doubt that this is happening in your program, but someone called addPet with a pointer, and they have no way of knowing if that pointer is valid again after calling. They have to consider it lost. It won't be freed if addPet() doesn't free it (and it doesn't), and they cannot safely do it themselves. The new memory you allocate doesn't get back to the caller in any way. Assigning to the local variable in addPet() doesn't affect any caller's variable. This realloc() is dangerous. The caller will absolutely lose the existing memory and has no way of obtaining the new memory.
Any of these issues can be the cause of your current problem; the others can be the cause of future problems.
Related
I made a simple base converter and the very first thing it does is it gets a string from the user. I wanted the string to be dynamic, so I did this:
char *getStr() {
char *str = NULL;
/*LOOP THAT USES getc() TO SAVE CHARS INTO THE STRING AND GROWS IT USING realloc() IF NEEDED*/
return str;
}
int main() {
char *str;
str = getStr();
//SOME STUFF HAPPENS HERE WITH THE str. IT REMAINS THE SAME LENGTH.
free(str) //IS THIS THE RIGHT PLACE TO FREE IT?
}
So, is free() in the right place here? I understand that since both str pointers point to the same address it should work, right? Also, by the way, how does free() know where to stop deallocating, when it only has the first address?
This may be pretty obvious, but I wanna make sure.
In your example, this is the right place for it.
The simplest free are called when you are just no need for the data anymore.
The tricky part in my experience is when things don't go as planned, or when your program can exit with multiple ways.
Take a look at this dummy program that has absolutely no real purpose.
A program that takes an integer, if this integer has a value superior to 10, the program will add 1 to it. If not, it will exit the whole program with exit (1)
static void add_one_to_num(int *num)
{
if (*num < 10)
{
free(num);
exit(1);
}
*num = *num + 1;
}
int main()
{
int *num;
num = malloc(sizeof(int));
printf("Enter a number higher than 10: ");
scanf("%d", num);
add_one_to_num(num);
printf("num: %d\n", *num);
free(num);
return (0);
}
In this program, we might exit at the function add_one_to_num so you need to free that pointer in that place.
By writing more and more programs you will get the hang of it, because it's actually very logical and not at all chaotic (It could be a pain in the neck for large programs to track down all allocated memories throughout all the functions the pointers are passed to ).
just make sure to free when you longer need the data, or when something changes the natural flow of your program.
I am writing a code while making the use of structures. I am new to structs so I am practicing to get used to it. Anyway while trying to use printf on a variable of type string which is a variable of a struct type, printf only prints '#' instead of the whole string.
...
void signPlayers(struct player players[9], int playersC) // players is an array declared on main and playersC is the size of it.
{
for (int i = 0; i < playersC; i++)
{
char tname[22];
printf("Please enter the name of the player #%d: \n",i+1);
int res = scanf(" %s",&tname);
while(res != 1)
{
printf("Please enter a valid name for player #%d: \n",i+1);
res = scanf(" %s",&tname);
}
players[i].name = tname;
printf("Player #%d signed as %s!\n\n",players[i].id,players[i].name); // this printf actually works fine
}
}
int checkForWinner(struct player players[], int playersC)
{
for (int i = 0; i < playersC; i++)
{
if (players[i].pos == 10)
return 0;
printf("%s\n",players[i].name); // prints "#" instead of the name
}
return 1;
}
...
so If I entered the name Joey, At first printf it actually prints "Joey", then when I call the checkForWinner function (Its called after signPlayers function), the printf now prints only "#" instead of the whole name again.
What could be wrong?
Your code is attempting to access stack memory after the function has returned. When you do that, you get just whatever garbage was left over after the function call, or perhaps some part of a new stack frame belonging to a different function. Either way, it's undefined behavior.
There are several problems with the assignment players[i].name = tname; in function signPlayers:
It is assigning the address of the local array variable tname. This variable goes out of scope on every iteration through the loop. That means accessing that pointer after the loop will be undefined behavior.
Even if tname was moved out of the loop's scope up to the function scope, it will still go out of scope as soon as the function returns. Either way, accessing it after the function call is undefined behavior.
It's likely that the address of tname doesn't change from one iteration of the loop to the next. So that means that the .name member of every player in the players array will probably be identical (as well as being invalid).
There are many ways to fix this. Here are three ways:
Make a copy of tname each time through the loop using strdup(tname), and assign that to players[i].name:
players[i].name = strdup(tname);
The strdup function allocates memory, so if you use this approach, you will need to remember to free the .name member of each player when you're done.
Allocate memory dynamically to each players[i].name prior to calling signPlayers:
for (i=0; i<playersC; ++i) players[i].name = malloc(22);
signPlayers(players, playersC);
// Don't forget to call free() on each .name member after you're done
Inside signPlayers, you would then get rid of tname altogether and do:
int res = scanf(" %s", players[i].name);
Note: No need to do 22 * sizeof(char) here, since the C standard guarantees sizeof(char) == 1. Also, I used 22 because that's what OP's code uses to declare tname. However, it should be noted that scanf is less than ideal here, because it gives no way to limit the number of bytes written to the array, and thus no way to protect against buffer overflow. If you type a name longer than 21 characters (need to leave 1 byte for the null terminator), then you will overflow tname and your program will either crash or silently corrupt your data.
Turn players[i].name into a sized char array instead of just a char pointer. In other words, statically allocate memory to each players[i].name. This has the advantage of not needing to call free, as you would need to do with methods 1 and 2. OP didn't show the definition of struct player, but this should suffice for example purposes:
struct player {
char name[22];
// other stuff
};
And again, inside signPlayers, you would scanf directly into players[i].name instead of using tname.
I have this struct Exam. and i am using cleanUp function to allocate and free the memory occupied by title but its not freeing it.
typedef struct
{
char* title;
Question* questions[MAX_QUESTIONS];
}Exam;
BOOL CleanUp(Exam * e){
char name[200];
printf("Enter name of the course \n");
gets(name);
fflush(stdout);
e->title = (char*)malloc(sizeof(strlen(name)+1));
strcpy(e->title,name);
free(e->title);
}
sizeof(strlen(name)+1) is not correct, this gives you the size of the result of that calculation, i.e. sizeof(int). Because you have allocated the wrong size you are writing past the end of the buffer.
This is corrupting data and causing free() to fail.
What you mean to do is:
sizeof(char) * (strlen(name) + 1)
In C, sizeof(char) is guaranteed to be 1, so you don't actually need it here, however I've put it there to illustrate the general way to allocate memory for multiple objects: multiply the size of the object by the number of objects.
Surely you simply meant:
e->title = strdup(name);
...
free(e->title);
strdup() will count the string pointed to by 'name', allocate space for a copy (including the null terminator) and copy the data in a sensible, architecture aligned way (usually.)`
I think Whilom Chime gave a pretty adequete answer, as did Mr. Zebra. Another way to do it would be like so;
e->title = malloc(sizeof(char *));
if(e->title != NULL) strcpy(e->title, word);
However, I've found when working with really large data sets (I had to put ~3M words into a 2-3-4 tree a couple days ago), e->title = strdup(word); is actually faster than strcpy(e->title, word);. I don't know why, and it honestly doesn't make sense to me, seeing as strcpy doesn't have to go through the process of allocating memory for the character pointer. Maybe someone else can give input on this
I've a file which contains names and grades of students, and I'd like to write a program which can sort their grades (like midterm 1,midterm 2) according to user choice. I wrote as far as the choice part and opening the file, yet I don't know how to make program read only certain part of the file (like only Midterm 1 grades for example) and sort them only. Here's what I've wrote so far;
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int number;
char name[30];
char surname[30];
int midterm1,midterm2,midterm3;
} Student;
int main()
{
int choice,studentnumber,midterm1,midterm2,midterm3;
char surname;
FILE *cfPtr;
struct student *name;
name = malloc( 10 * sizeof(Student));
if ((cfPtr = fopen("grades.txt", "r")) == NULL)
printf("File cannot be opened.\n");
else {
const int STUDENTSMAX = 100;
Student students[STUDENTSMAX];
int i = 0;
while (!feof(cfPtr))
{
fscanf(cfPtr, "%d%s%s%d%d%d", &students[i].number, &students[i].name,&students[i].surname, &students[i].midterm1, &students[i].midterm2, &students[i].midterm3);
printf("%4d%15s%15s%10d%10d%10d\n", students[i].number, students[i].name,students[i].surname, students[i].midterm1, students[i].midterm2, students[i].midterm3);
i++;
}
printf("What would you like to do? \n"
"1- Sort according to midterm 1\n"
"2- Sort according to midterm 2\n"
"3- Sort according to midterm 3\n"
"4- Exit\n");
scanf("%d",&choice);
while (choice != 4);{
switch (choice) {
case 1:
qsort(students,10,sizeof(int),comp);
for (i=0; i<9; i++)
printf("%4d%15s%15s%10d%10d%10d\n", students[i].number, students[i].name,students[i].surname, students[i].midterm1);
fclose(cfPtr);
}
system("PAUSE");
return 0;
}
Given what might be a somewhat free form text file (based on the shown code), it probably makes sense just to read the entire file (somewhat like you are already doing) and only use the parts that you need. If the text file has a very specific format with fixed offsets, you could seek to certain locations in the file and read a specific column value, then seek to the next offset and read the column value from the next row. But that is probably more trouble than it is worth and would not be much more efficient (if at all).
Having said that, to sort the results, you probably need the entire file anyway. For example, if you just read and sort the "midterm 1" value, then the result would just be sorted grades without any associated name and student number. So without knowing more about the goal, you might consider creating a struct that can hold a single row (student number, name, surname, midterm1, etc.). Then create an array of those and read each row into an element of the array. If you know how many rows exist up front, you can allocate the array in one chunk, otherwise you might need to reallocate it as you go to grow it.
Once you have read the entire array, you could sort based on the desired value (e.g., with qsort.
Having mentioned that, there a few problems/issues with the existing shown code:
The second printf has fewer format specifiers (%s) than parameters.
The third printf with the "What would you like to do" question is missing the closing paren.
The fprintf is incorrect; it should have a file handle as the first parameter. I suspect, though, that it was maybe meant to be printf?
The final while loop has an extraneous semicolon (;) following its closing paren, which means that it has an empty body rather than the apparently intended printf and switch statement.
The switch statement is a bit odd as written. I assume that is the "unfinished" part. But including the fclose in it seems strange. It should probably be at the end of the main else.
Using system("PAUSE"); is maybe not the best choice. Perhaps using getch would make more sense to pause for input.
Edit Here is some additional information in response to your comment asking for more details. This sounds like homework to me, so it doesn't seem right just to give the answer. But here is one way to do it:
Define a struct with the 6 items that are in the file (basically put in the 6 variables that you currently have defined as local variables).
Declare a local variable (e.g., grades) as a pointer to struct that you defined.
Use malloc to allocate memory and assign it to the pointer just mentioned. The amount of memory is perhaps the trickiest part of this whole thing. The size parameter to malloc will be something like numRecs * sizeof( yourstruct ). The question is what numRecs should be. If this is an assignment and you were told how many records there would be (a maximum), then just use that. If, though, it is "unknown", then there are a couple of ways of dealing with that. One is to just guess at a number (e.g., 100) and then while reading them in a loop use realloc if you exceed 100. The other alternative (probably less efficient) would be to use two loops - read through them once without storing them but just count them and then allocate the known size and read them again. I would use the realloc version.
Replace the use of the local variables with the array (that was malloced). For example, instead of studentnumber you would use grades[arraypos].studentnumber. While you read them in, keep a counter of how many there are. You can then use qsort to sort the array.
Edit 2
Your struct definition looks correct except that the FILE *cfPtr; member should not be in it. It should still be a local variable.
For ease of use, you can define the struct as typedef struct { ... } Student;. That way you can just use Student instead of struct Student in the code. Note that I capitalized the name (personal preference in naming to make it not look like a variable name).
For the malloc, you are close. But as written, it is allocating space for a single record. You would need something like this: name = malloc( 50 * sizeof( struct student )); (or malloc( 50 * sizeof( Student )); if you change it to use the typedef. That assumes there would not be 50 or fewer records to read from the file.
For an assignment, I have to declare a struct as follows:
struct Food
{
char *name;
int weight, calories;
} lunch[5] = {
{
"apple", 4, 100
},
{
"salad", 2, 80
}
};
In my main, I am trying to ask the user for the rest of the inputs to fill the struct to print them out. I figure I would try to use malloc. Would I do something like this?
int main(void)
{
char *str1;
printf("Please enter a food, weight, and calories of the food: ");
scanf("%s", (char *)malloc(str1));
return(EXIT_SUCCESS);
}
Well ... Not quite.
You just pass the result of malloc() to scanf(), and that function won't return it, you lose the pointer. This is generally a bad idea. Also, investigate what argument malloc() expects, you're not doing it right.
Consider first allocating the memory, using a pointer variable to store it, and then passing the value of that pointer to scanf(). Hint: you already have the pointer variable, in your array.
Also, you shouldn't cast the return value of malloc() in C, and return is not a function, so it shouldn't have parenthesis around its value.
You should rather allocate space for a new instance of Food, then allocate space for name. If all allocations succeed, then you can start asking the user for data.
You should look at your course material, read the books on the reading-list, or ask your lecturer.
If you must do your own research online, you should perhaps try to understand this example and google for similar how-tos.
Generally, you shouldn't use scanf for parsing user input. Its much more straightforward and robust to use getline to retrieve a line of user input, then utilities such as strdup and atoi to extract your values, one per line rather than on the same line, and check for error values and such.
Here is a small example of how I would go about this. Unfortunatley I cannot test the code and I am learning C myself at the moment, but maybe this will give you some ideas. This would just initialize the instance at lunch[2]. So you should add some sort of loop to fill the other instances.
int main(void)
{
lunch[2].name = (char*) malloc(/*The size of the string you want for the
name of your food. Every character has
the size of one byte (+'\0' at the end)*/)
printf("Please enter a name for your food: ");
scanf("%s", lunch[2].name);
printf("Please enter the weight of your food: ");
scanf("%d", &lunch[2].weight;
printf("Please enter calories of your food: ");
scanf("%d", &lunch[2].calories);
return EXIT_SUCCESS;
}
getline, strtok, strdup, atoi
the rest is left as an exercise :-)