Making generic function to load items into linked list - c

OK beginning C programmer here. What I'm attempting to do is create a function that populates a linked list from a text file. What I've done so far is to use fgets() and strtok() to iterate through the text file and I'm trying to load the tokenized strings into a function to populate the linked list. First off, when I use strtok, how do I capture the tokenized strings into char arrays or strings? So far, I've tried something like this:
char catID[ID_LEN+1];
char drinkType[1];
char itemName[MAX_NAME_LEN + 1];
while((fgets(line, sizeof(line), menufile)) != NULL) {
token = strtok(line, "|");
strcpy(data,strdup(token));
addCatNode(menu, catID);
printf("%s\n", catID);
i++;
while(token){
if(token)
{
strcpy(drinkType,strdup(token));
addNodeItem(&menu, drinkType);
strcpy(itemName,strdup(token));
addNodeItem(&menu, itemName);
token = strtok(NULL, "|");
}
}
}
but somehow I don't think that's the right approach. And of course when I try and load the data into the addNodeItem() function, whose prototype I've written like this:
void addNodeItem(BCSType* menu, char *nodeitem);
and try and add the item using this notation:
category->nodeitem
the compiler tells me that there is no member named 'nodeitem' in the struct. Of course there isn't, but I'm trying to load the name from the strtok() part, so how do I get the addNodeItem() function to recognize the name that I'm trying to pass into it? Very confused here.
The "category" struct in the linked list looks like this:
typedef struct category
{
char categoryID[ID_LEN + 1];
char categoryName[MAX_NAME_LEN + 1];
char drinkType; /* (H)ot or (C)old. */
char categoryDescription[MAX_DESC_LEN + 1];
CategoryTypePtr nextCategory;
ItemTypePtr headItem;
unsigned numItems;
} CategoryType;

There are several issues here, but for starters, strcpy(drinkType, strdup(token)) doesn't make a lot of sense.
token is a pointer to a portion of your input string, with the '|' separator replaced by a NULL.
strdup allocates strlen(token) worth of memory and copies the contents of token. So far so good. It returns the address of the new memory, which you don't store anywhere, so you can never free() it. This is a leak, and you could eventually run out of memory.
strcpy(drinkType,strdup(token)) copies that new memory into the memory pointed to by drinkType. Thats only 1 character. I don't know if that is big enough. Neither do you, since there could be anything in the file you are loading. This is a bug waiting to happen.
And then it seems like the addNodeItem() function is missing something. You are passing a value that represents one of the possible values in the structure, with no way of specifying which one. You would have better luck creating a local copy of CategoryType, assigning all the information from the tokenizer, and then copying the whole thing into a new node.
outline of algorithm:
while lines:
CategoryType newCat
tokenize line
copy tokens into correct members of newCat, using `strncpy` to ensure no overruns.
add newCat to linked List.
For the last step, you need to duplicate newCat before adding it, because it will be overwritten when you process the next line. You can either pass a copy to AddNodeItem, or have AddNodeItem make the copy. (There are other options, but these are probably most straightforward.)

Related

Dynamic fscanf into an array

I am trying to place some text into a structure part of my array is a array which takes part of the text.
For example my structure is:
struct animal
{
char animal_Type[11];
int age;
int numberOfLegs;
int walksPerDay;
char favoriteFood[];
};
I will then have input such as:
dog,2,4,2,biscuits,wet
cat,5,4,0,biscuits,wet,dry,whiskers
bird,1,2,0,birdseed,biscuits,bread,oats,worms,insects,crackers
I have a working solution that places all the values up to walks per day into the structure, however I want to be able to place the food items into Favorite food. I have a dynamic array for this, but i'm not sure how to read remaining text into the favoriteFood array.
The code used is:
fp = open("animals.txt","r");
struct animal *animal = malloc(sizeof(sturct animal)*3);
int i = 0;
if(fp != NULL) {
while(i < 3) {
fscanf(fp,"%s %d %d %d %s",
animal[i].animal_Type,
animal[i].age,
animal[i].numberOfLegs,
animal[i].walksPerDay,
animal[i].favoriteFood); // need to be able to enter the string of food into here
i++
}
How would I go about doing this?
First of, your struct doesn't match what you've said in the comments.
char favoriteFood[];
The above is an array of char, so couldn't possibly hold a list of favourite foods except if it were one string. And since the size of the array is unspecified, you'd not be able to fill it like you have been either. Instead what you actually want is
char **favoriteFood;
unsigned int favoriteFoodSize;
That will let you create an expanding list of strings to fit whatever data you need to accommodate.
As for reading it in, the best way would be to read the entire line in using fgets and then use something like strtok to break the line up by your separator character. First define a very large string to hold the entire line and a char * to hold each field.
char buffer[1024];
char *token;
And then to the main loop would be something like this:
while(fgets(buffer,1024,fp)) {
token=strtok(buffer,",");
strcpy(beasts[i].animal_Type,token);
token=strtok(NULL,",");
beasts[i].age = atoi(token);
/* etc... */
}
You'd need to check whether token is ever NULL to cope with the possibility of short lines and handle it accordingly. And also make sure that the string copied into animal_Type isn't longer than 10 characters...or alternative make it a char * so you can have any size of string.
For the favoriteFood, you'll need to use realloc to increase the size of it to accommodate each new food added and keep going through the string until you run out of tokens.
token=strtok(NULL,",");
if(token) {
beasts[i].favoriteFood=malloc(sizeof(char *));
beasts[i].favoriteFood[0]=strdup(token); // Need to index using 0 as favoriteFoodSize won't have a value yet
beasts[i].favoriteFoodSize=1;
token=strtok(NULL,",");
while(token) {
beasts[i].favoriteFood=realloc(beasts[i].favoriteFood,(beasts[i].favoriteFoodSize+1)*sizeof(char *));
beasts[i].favoriteFood[beasts[i].favoriteFoodSize]=strdup(token);
beasts[i].favoriteFoodSize++;
token=strtok(NULL,",");
}
}
The last food will have a \n in it as fgets keeps it in the buffer it reads, so you could use that to tell if you've finished processing all the foods (you will also need to remove it from the last food). Or if you don't have it, you know the line was longer and you'll need to read more in. But that seems unlikely based on your sample data.
And since you're doing lots of memory allocation, you should ensure that you check the values returned to make sure you've not run out of memory.

Absorb equal strings onto the same pointer

I'm making a program that reads a text file composed by strings, each one on a line. Basically I do this:
...
char* name;
char* buffer = malloc(sizeof(char) * SIZE); //size is a defined constant in the header
while(fgets(buffer, SIZE, pf)){ //pf is the opened stream
name = malloc(sizeof(char) * SIZE);
strcpy(name, strtok(buffer, "\n"));
manipulate(name); //call an extern function
}
Function manipulate is declared in this manner:
void manipulate(void* ptr);
The problem is that in this way two equal strings will have different memory addresses so they will recognized as two different elements from manipulate function.
How can I make them recognized as a single element?
Store the strings in a set, a data type which stores no repeated values and is fast to search. Basically it's a hash table where the key is the string and the value doesn't matter.
You can write your own hash table, it's a good exercise, but for production you're better off using an existing one like from GLib. It already has convenience methods for using a hash table as a set. While we're at it, we can use their g_strchomp() and g_strdup().
#include <stdio.h>
#include <glib.h>
int main () {
// Initialize our set of strings.
GHashTable *set = g_hash_table_new(g_str_hash, g_str_equal);
// Allocate a line buffer on the stack.
char line[1024];
// Read lines from stdin.
while(fgets(line, sizeof(line), stdin)) {
// Strip the newline.
g_strchomp(line);
// Look up the string in the set.
char *string = g_hash_table_lookup(set, line);
if( string == NULL ) {
// Haven't seen this string before.
// Copy it, using only the memory we need.
string = g_strdup(line);
// Add it to the set.
g_hash_table_add(set, string);
}
printf("%p - %s\n", string, string);
}
}
And here's a quick demonstration.
$ ./test
foo
0x60200000bd90 - foo
foo
0x60200000bd90 - foo
bar
0x60200000bd70 - bar
baz
0x60200000bd50 - baz
aldskflkajd
0x60200000bd30 - aldskflkajd
aldskflkajd
0x60200000bd30 - aldskflkajd
If you indeed have two strings then they necessarily have different addresses, regardless of whether their contents are the same. It sounds like you want to keep track of the strings you've already read, so as to avoid / merge duplicates. That starts with the "keeping track" part.
Evidently, then, you need some kind of data structure in which to record the strings you've already read. You have many choices for that, and they have different advantages and disadvantages. If the number of distinct strings you'll need to handle is relatively small then a simple array or linked list could suffice, but if it is large enough then a hash table will provide much better performance.
With that in hand, you check each newly-read string against the previously read ones and act accordingly.

Array of strings reading its input from file.txt C programming

I am having a problem and cant tell what is it.
struct arrayDB {
char *user[MAX_SIZE];
char *pass[MAX_SIZE];
char db[10][2];
};
void readFile(char fileName[100])
{
char* word ;
char line[90];
FILE *passFile;
int rowC=0;
int chk=0;
passFile=fopen(fileName,"rt");
while(fgets(line,90,passFile)!=NULL)
{
word=strtok(line," ");
rowC=rowC+1;
while(word!=NULL)
{
printf("Count=%i \n",rowC);
if(chk==0)
{
printf("word:%s\n",word);
DB.user[rowC]=word;
chk=1;
}
else
{
printf("word:%s\n",word);
DB.pass[rowC]=word;
}
printf("r=%s , c=%s\n",DB.user[rowC],DB.pass[rowC]);
word=strtok(NULL," ");
}
chk=0;
}
int i;
for(i=1; i<6;i++)
{
printf("- %s , %s \n",DB.user[i],DB.pass[i]);
}
}
but the output I am getting that all the array elements is the same value which is the last word in the file
as you can see in the pic
thanks
You're reading every line into the same string line. Then when you use strtok(), it's returning pointers into this string, and you're storing these pointers into DB. So all the records in DB are pointing to locations in line, which gets overwritten each time you read another line from the file. When everything is done, line contains the contents of the last line of the file, and all the DB entries point to that.
Another problem is that line is a local variable, and pointers to it become invalid when the function returns.
To solve both problems, you need to make copies of the string and store these in DB. For example:
DB.user[rowC]= strdup(word);
This also means that when you're done with a DB record, you need to call free(DB.user[i])
Some suggestions:
First, learn to use a debugger. There are free ones, get one and turn it on to find all of these errors (that is what I did here)
Next, for the code example you show to compile, the struct definition needs to support your code (currently, DB is not defined)
typedef struct
{
char *user[MAX_SIZE];
char *pass[MAX_SIZE];
char db[10][2];
}arrayDB;
arrayDB DB;//define DB
Next,
you need to allocate space for your string arrays:
something like:
for(i=0;i<MAX_SIZE;i++ )
{
DB.user[i] = malloc(100);
DB.pass[i] = malloc(100);
}
Next, don't forget to free them when done using them.
for(i=0;i<MAX_SIZE;i++ )
{
free(DB.user[i]);
free(DB.pass[i]);
}
Next, you cannot assign a string using an equal operator:
DB.pass[rowC]=word;
use strcpy (or some other string function) instead:
strcpy(DB.pass[rowC],word);
Next, this line:
printf("r=%s , c=%s\n",DB.user[rowC],DB.pass[rowC]);
Is called after a conditional statement where either DB.user[rowC] or DB.pass[rowC] will be written to, never both. Suggest splitting this printf statement to print one or the other, and place it into the appropriate conditional branch.

Char* array not retaining value C

For my networking class, we're building a bittorrent client based off the UDP protocol, which is pretty cool but I'm having a ton of trouble with C strings for some reasons.
The first time I receive a packet, I do:
if(server_data == NULL){
server_data = malloc(one_block.total_blocks*sizeof(char*));
int i;
for(i = 0; i < one_block.total_blocks; i++){
server_data[i] = malloc(sizeof(char*));
server_data[i] = "";
}
}
Here, server_data is a char** and one_block is struct that holds packet information and the payload.
Next I do:
server_data[one_block.which_block] = one_block.payload;
blocks_rcv++;
if(blocks_rcv == one_block.total_blocks-1)
done = TRUE; //macro
if(done){
int i;
for(i = 0; i < one_block.total_blocks; i++){
printf("%s", server_data[i];
}
}
All seems well and dandy but for whatever insane reason when I print the contents of server_data before all the packets are received, I see different data from each packet. Afterwards I set done = TRUE and go into that for loop, every spot in the array contains the same string value.
I have no idea why this is happening and I really want to understand how from the beginning of the post to the end, the contents of the array change, even though I verify them through every iteration of the loop that reads in one packet at a time.
This line is the problem:
server_data[i] = "";
It overwrites the allocated pointer, with a pointer to the string literal. And as string literals can't be modified, if you later copy into this pointer, you experience undefined behavior.
If you want to make sure the string is empty, either use calloc, set the first character to '\0', or use strcpy to copy in the new string.
There are a couple of issues going on here:
1) First, server_data, if it's declared as a char**, may or may not be null off the bat, unless you declare it so. I'm not sure if you initialized it to NULL or not. It's a good idea to explicitly initialize it to NULL.
2) I'm not sure from what's going on if you intend for each item of the array server_data to hold a char* (in other words, a reference to a string), or for the array to be a string itself. Is one_block.payload a string, or a set of pointers to strings?
I ran your code with some test values and I'm personally not getting any problems with unexpected values...I think the issue may be in how the struct that holds your payload data is set up. Could you show us your one_block struct? What type of variable/array is one_block.payload?

Pointer problem in C for char*

i use pointer for holding name and research lab property. But when i print the existing Vertex ,when i print the vertex, i cant see so -called attributes properly.
For example though real value of name is "lancelot" , i see it as wrong such as "asdasdasdasd"
struct vertex {
int value;
char*name;
char* researchLab;
struct vertex *next;
struct edge *list;
};
void GRAPHinsertV(Graph G, int value,char*name,char*researchLab) {
//create new Vertex.
Vertex newV = malloc(sizeof newV);
// set value of new variable to which belongs the person.
newV->value = value;
newV->name=name;
newV->researchLab=researchLab;
newV->next = G->head;
newV->list = NULL;
G->head = newV;
G->V++;
}
/***
The method creates new person.
**/
void createNewPerson(Graph G) {
int id;
char name[30];
char researchLab[30];
// get requeired variables.
printf("Enter id of the person to be added.\n");
scanf("%d",&id);
printf("Enter name of the person to be added.\n");
scanf("%s",name);
printf("Enter researc lab of the person to be added\n");
scanf("%s",researchLab);
// insert the people to the social network.
GRAPHinsertV(G,id,name,researchLab);
}
void ListAllPeople(Graph G)
{
Vertex tmp;
Edge list;
for(tmp = G->head;tmp!=NULL;tmp=tmp->next)
{
fprintf(stdout,"V:%d\t%s\t%s\n",tmp->value,tmp->name,tmp->researchLab);
}
system("pause");
}
When you do this:
newV->name=name;
newV->researchLab=researchLab;
You are copying the pointer to the strings name and researchLab. You are not copying the strings themselves. In other words, after this, newV->name and name point to exactly the same location in memory where the name is stored; you have not created a duplicate copy of the data.
Since you then proceed to overwrite the name array in the createNewPerson function, at the end of this function, all of your vertex structs will have their name attribute pointing to the same memory location, which is only storing the last name entered.
Worse, when createNewPerson returns, its local name array goes out of scope, and is re-used for other things. Since your vertex structs are still pointing here for their name attributes, this is how you get garbage.
You need to duplicate the string. A simple way to do it is:
newV->name = strdup(name);
You will need to #include <string.h> to get the strdup library function.
And then you also need to make sure that you call free on the name attribute whenever you are disposing of a vertex structure.
GRAPHinsertV copies the pointer of the name and researchLab strings to the vector structure.
createNewPerson creates a temporary for the name and researchLab strings.
The problem here is, you're pointing to a temporary string which causes undefined behaviour when you access it after createNewPerson returns.
To solve this problem, you can duplicate the strings in GRAPHinsertV using malloc+strcpy, or by using the non-standard strdup.
The name variable you pass to GRAPHinsertV() is allocated on the stack for createNewPerson(), so the pointer points to a local variable. Once the activations records are popped off that value can (and will) be overwritten by subsequent code.
You need to allocate memory on the heap if you are only going to keep a char * in the struct.
Ex. Instead of
char name[30];
you could use
char *name = (char *)malloc(30*sizeof(char));
but keep in mind if you manually allocate it you have to take care of freeing it as well, otherwise it will have a memory leak.
When you assign the char *name pointer, like
newV->name=name;
You're not creating a new string, but making the newV.name member point to the same memory as the char[] array that was passed in. You'll need to malloc() or otherwise allocate a new char[] array in order to obtain separate storage for each structure.
There's a problem here:
Vertex newV = malloc(sizeof newV);
It should be
Vertex *newV = malloc(sizeof(Vertex));
You are allocating memory in the function createNewPerson() that lasts exactly as long as createNewPerson() is executing, and is available for overwriting immediately after it returns. You need to copy the text fields in with something like strdup(newV->name, name), rather than point to the local variables in createNewPerson(). (If your implementation doesn't have strdup(), you can easily define it as:
char * strdup(const char *inp)
{
char * s = malloc(strlen(inp) + 1);
strcpy(s, inp);
return s;
}
In addition, your I/O has potential problems. If you enter my name, "David Thornley", for the name, it'll take "David" as the name and "Thornley" as the lab, since "%s" searches for a whitespace-delimited string. If I enter "Forty-two" for the ID, nothing will be put in id, and "Forty-two" will be used for the name. If I enter a name or lab name over 29 characters, it will overwrite other memory.
I'd suggest using fgets() to get one line of input per answer, then use sscanf() to parse it.
When passing and assigning strings, always make a copy of them. There're no guarantees that the string you received is still in the memory afterwards, since the pointer could have been freed.
Of course, if you are only going to use name inside the function (that's, you're not going to assign it to a variable outside the scope of the function), you don't have to do the copy.
In order to do that, inside GRAPHinsertV, instead of
newV->name=name;
do
if (name != NULL) // Preventing using null pointer
{
newV->name = malloc(strlen(name)+1);
strcpy(newV->name, name);
}

Resources