I tried many solution for this issue, but none worked properly!
I want to copy value of char** array to a variable of type char*.
char *line;
char **tokens = malloc(....);
So, I tried the following:
for(i=0; i < sizeof(tokens); i++)
strncpy(line, tokens[i], strlen(line));
Or
for(i=0; i < sizeof(tokens); i++)
strncat(line, tokens[i]);
Or
for(i=0; i < sizeof(tokens); i++)
memcpy(line, tokens[i], strlen(line));
My understanding is that tokens[i] would be of type char*, but what I couldn't understand if the error I'm getting.
Segmentation fault (core dumped)
If these ways won't work, how can I do the copying?
Any hints?
char *removesubString(char *path, char **args){
char *dir;
int COUNT;
COUNT = 100;
char **dirs = malloc(sizeof(char*)*COUNT);
int i, position;
for (i = 2; i < sizeof(args); i++) {
if(args[i] == NULL){
break;
}
dir = strtok(path, PATH_DELIM);
position = 0;
while (dir != NULL) {
if(strcmp(dir, args[i]) == 0){
dir = strtok(NULL, PATH_DELIM);
continue;
}
dirs[position] = dir;
position++;
dir = strtok(NULL, PATH_DELIM);
}//end of while
dirs[position] = NULL;
}//end of for
char *line;
line = "";
for (i = 0; i < position; i++) {
strncpy(line, dirs[i], strlen(dirs[i]));
}
return line;
}
The first issue that pops up about your code is that you're wrong with the boundaries:
char *line;
char **tokens = malloc(TOKENS_ARRAY_SIZE);
when you do:
for(i=0; i < sizeof(tokens); i++) {
…
}
it's not returning the size of the allocated memory, but the allocated memory for the tokens pointer itself. From the sizeof manpage:
Returns the size, in bytes, of the object representation of type
It happens that when you do sizeof on a static matrix, it will return the size of the matrix because that's the amount of allocated memory for it. But for a dynamically allocated matrix, it will only return the size of the pointer, i.e. if you do:
char array_static[42];
char* array_dyn = malloc(sizeof(char)*42);
printf("sizeof(array_static) = %d\n", sizeof(array_static));
printf("sizeof(array_dyn) = %d\n", sizeof(array_dyn));
it will return:
sizeof(array_static) = 42
sizeof(array_dyn) = 8
so if the number of items within your dynamic array is less than the returned size of the array's pointer, you'll overflow and you'll get a segfault.
So the right way to handle your situation, is to keep the length of the dynamic array in another variable, update it as you're setting up the size of the allocated memory, and then use that value for iterations.
int tokens_length = 42;
char *line;
char **tokens = malloc(sizeof(char*)*tokens_length);
for(i=0; i < sizeof(tokens_length); i++) {
…
}
so in your case, you should be doing:
// keep somewhere the number of directories that you *can* allocate
int nb_dirs = 100;
char **dirs = malloc(sizeof(char*) * nb_dirs);
…
// keep a pointer on how many directories you've added
int position = 0;
while (dir != NULL) {
…
position++;
// fail loudly if you're adding more directories than you've allocated
// or you might use realloc() to extend the array's length
if (position >= nb_dirs) {
printf("ERROR! Too many directories!");
// RETURN WITH ERROR!
}
…
}
// here you iterate over all the directories you've added
for(i = 0; i <= position; i++){
// here you can do stuff with dirs, and copy only the length of the dirs element
strncpy(<TARGET>, dirs[i], strlen(dirs[i]);
}
Then there's another issue you should think about: in your loop, you're modifying path, given as an argument, where you're strcpy()ing dirs into:
strncpy(path, dirs[i], <LENGTH>);
But that makes little sense, whatever you're trying to do is not what you've written.
Here, considering that the size argument is correctly set, you'd be copying each item of the dirs array into the same variable. So you'd end up having always the last value of the dirs array referenced at the path pointer.
But the issue is that you only have the path pointer, but you know little about how it has been allocated when it's been given to the function. How has it been allocated, and how much memory was allocated? What "useful" size is it (though that one can be guessed with strlen())?
Oh, and finally, don't forget to free() your allocations once you're done with them. Do not leak memory, that's rude! ☺
edit:
ok, here are stuff I can see that are wrong, and some comments about it:
char *removesubString(char *path, char **args){
char *dir;
int COUNT = 100;
char **dirs = malloc(sizeof(char*)*COUNT);
int i, position;
/* for both XXX marks below:
*
* below, sizeof(args) will return the size of the pointer
* not the number of items it contains. You *NEED* to pass
* a "int argc" as parameter to your function, that gives
* the numbers of items in the array.
* Think about why you have argc in the main() function construct:
* int main(int argc, const char** argv)
* _OR_ if the args array of strings is _ALWAYS_ terminated
* by a NULL item, then you should do: */
// int i = 0;
// while(args[i] != NULL) {
// /* do stuff */
// ++i;
// }
for (i = 2; i < sizeof(args) /* XXX */; i++) {
if(args[i] == NULL){ /* XXX */
break;
}
dir = strtok(path, PATH_DELIM);
position = 0;
while (dir != NULL) {
if(strcmp(dir, args[i]) == 0){
dir = strtok(NULL, PATH_DELIM);
continue;
}
/* because within the function you have no guarantee
* on the number of tokens within path, if you have
* more than 100 tokens, you will overflow the dirs array.
* a good idea would be to test whether position is bigger
* than or equal to COUNT, and if it is use realloc to
* extend dirs */
dirs[position] = dir;
position++;
dir = strtok(NULL, PATH_DELIM);
/* you could avoid having twice the former line
* and instead make your loop body being: */
// while (dir != NULL) {
// if(strcmp(dir, args[i]) != 0){
// /* TODO: check position vs COUNT and realloc dirs if necessary */
// dirs[position] = dir;
// ++position;
// }
// dir = strtok(NULL, PATH_DELIM);
// }
}
dirs[position] = NULL;
}
char *line;
line = ""; /* ← here you allocate line with a string of length 1 */
for (i = 0; i < position; i++) {
// so here, you'll write up to the number of characters
// within dirs[i], into a string of length one.
strncpy(line, dirs[i], strlen(dirs[i]));
}
/* And even if it was working you'd be assigning the line
* variable to a new value at each iteration, ending up doing
* something equivalent to the following line, but with "position"
* numbers of iterations: */
// strncpy(line, dirs[position-1], strlen(dirs[position-1]));
/* Don't forget to free the instances you've allocated dynamically
* before leaving the function: */
// free(dirs);
/* And finally there's another issue here, you're returning
* a variable that has been statically allocated above, so that
* when you'll try to use the pointed instance in the calling
* context, that variable won't exist anymore. */
return line;
}
HTH
Related
I want to write a function that adds an Entry to the first free slot of an array list (if there is a slot free) and a function that merges all those entries to one "String" (I know there are no strings) separated by ', '.
Example list contains: "Anna", "Alex", "Anton" in this order
Output:"Anna,Alex,Anton".
This is the .c file (only the toDo's should be done)
/** The function adds an entry name to the array list at the
* first free position, if such a position exists.
* If there is no free space, ie no NULL entry,
* the array remains unchanged. When inserting a name,
* First, the same amount of memory is allocated on the heap using malloc
* (note space for a final \ 0). After that
* we use strncpy to convert the string name into the allocated
* Memory area copied.
* #param list: Array of \0 terminated strings
* #param listsize: size of array
* #param name: \0 terminated string that should be copied into the array
*/
void addNameToList(char **list, size_t listsize, char *name) {
//toDo
}
/** The function adds all entries in the list array to one separated by ','
* zero-terminated string that is stored in destbuffer.
* Entries with the value NULL are not added.
* So that chained calls are possible, the function returns the
* Pointer destbuffer as return value.
* Example: The list contains the names "Anna", "Alex", "Anton" in this
* Sequence. The function returns "Anna, Alex, Anton".
* #param list: array of \0 terminated strings
* #param listsize: size of array
* #param destbuffer: destinationbuffer
* #param buffersize: size of destbuffer
* #return destbuffer
*/
char *printListToString(char **list, size_t listsize, char *destbuffer, size_t buffersize) {
//toDo
return destbuffer;
}
void freeList(char **list, size_t listsize) {
size_t index = 0;
for (index = 0; index < listsize; index++)
if (list[index] != NULL)
free(list[index]);
free(list);
}
char **initList(size_t size) {
return calloc(size, sizeof(char *));
}
This is the given Main (not to change):
int main(void) {
char **names;
char outputbuffer[100];
names = initList(LIST_SIZE);
addNameToList(names, LIST_SIZE, "Alice");
addNameToList(names, LIST_SIZE, "Bob");
addNameToList(names, LIST_SIZE, "Carla");
addNameToList(names, LIST_SIZE, "Dana");
addNameToList(names, LIST_SIZE, "Eve");
printListToString(names, LIST_SIZE, outputbuffer, 100);
printf("%s\n", outputbuffer); //Output: Alice,Bob,Carla,Dana,Eve
freeList(names,LIST_SIZE);
}
What I have tried so far (not working):
char *printListToString(char **list, size_t listsize, char *destbuffer, size_t buffersize) {
int k = 1;
for(int i = 0; i < listsize; i++) {
if(list != NULL) {
strcpy_s(destbuffer, sizeof list, list);
}
if(list == '\0') {
destbuffer[i] =',';
destbuffer[k] = ' ';
}
k++;
}
return destbuffer;
}
The code above:
k is always one step ahead of i, so it can add a space right after the ',' (which is added at i)
I iterate thru the list and check whether an entry is NULL or not if its not NULL it should copy the name from the list into the destbuffer
Since the names end with \0 I thought I can just add , and a space right after I copied the name
void addNameToList(char **list, size_t listsize, char *name) {
malloc(sizeof name);
if(sizeof list != listsize) {
for(int i = 0; i < listsize; i++) {
if(list[i] == NULL) {
list[i] = name;
}
}
}
}
the code above:
saving memory for the name
check if list is full
if not I add the name at the first place thats null
(Note that I dont have any experience in C, only Python and Java. The "code above" section is what the code meant to do, not what its actually doing)
Let's consider the last snippet
void addNameToList(char **list, size_t listsize, char *name) {
// ^^^^^^^^^^
// 'name' is a POINTER. Not an array, nor a C-string (NULL-terminated array
// of char). It may point to the first element of one of those, though.
malloc(sizeof name);
// ^^^^^^^^^^^ This returns the size, in bytes, of a POINTER to char.
// Maybe 8, but surely not the number of characters up to the first '\0'
// from the one pointed by 'name'. To get that, you need
// strlen(name)
// Besides, 'malloc' RETURNS a pointer and you NEED it, because the whole
// point of that function call is to allocate enough memory to store
// your object and you better know WHERE this space is. If only to later
// release that resorce and avoid any leak.
if(sizeof list != listsize) {
// ^^^^^^^^^^^ Again, it's the size of a pointer to a pointer to char...
// Unfortunately, it's also probably different from listsize, so that
// it's harder to catch this error. Also, You don't need this check...
for(int i = 0; i < listsize; i++) {
// ^^^^^^^^^^^^ ... Because there already is this.
if(list[i] == NULL) {
list[i] = name;
// ^ This is a shallow copy, only the value OF the pointer
// (let's say the memory address) is copied, NOT all the elements
// of the string.
// Here is where the memory should be allocated, something like
// size_t size = strlen(name);
// list[i] = malloc(size);
// if ( list[i] == NULL ) {
/* malloc may fail... */
// }
// Then, the array must be copied. Your teacher mentioned
// 'strncpy'.
}
}
}
}
The other function, printListToString is broken too, perhaps even worse than the previous one.
k is always one step ahead of i, so it can add a space right after the ',' (which is added at i)
To do that, i + 1 could be enough, but that's not the point. There should be two different indices, one to itarate over list and one for destbuffer.
I iterate thru the list and check whether an entry is NULL or not if its not NULL it should copy the name from the list into the destbuffer
Unfortunately, in all its occurrences, list is written without an [i], so that no name in the list is actually accessed.
Since the names end with \0 I thought I can just add , and a space right after I copied the name
I'd do something like
char *printListToString( char **list, size_t listsize
, char *destbuffer, size_t buffersize) {
size_t j = 0;
for (size_t i = 0; i < listsize; ++i) {
if ( list[i] != NULL ) {
// Add ", ", but only if it's not the first name.
if ( j != 0 ) {
if ( j + 2 > buffersize )
break;
destbuffer[j++] = ',';
destbuffer[j++] = ' ';
}
// Copy the name. Feel free to use a library function instead.
for ( size_t k = 0; list[i][k] != '\0' && j < buffersize; ++k, ++j ) {
destbuffer[j] = list[i][k];
}
}
}
destbuffer[j] = '\0';
return destbuffer;
}
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define stock_dir "/Users/myname/prices/"
#define file_list "/Users/myname/trade/trade/nasdaq100_stock_list.txt"
#define look_back_period 3
#define num_stocks 103
#define days_of_data 21
int main()
{
FILE *stocks, *stk;
char stock[11], fullpath[50] = "\0", header[25];
char line_of_data[40];
char *sclose, *svol;
int n = 0, i = 0;
typedef struct daily_data {
char *date;
float close;
int vol;
}data;
sclose = (char*) malloc(20*sizeof(char));
svol = (char*) malloc(20*sizeof(char));
data** day_data = (data**) malloc(num_stocks*sizeof(data*) );
if (day_data == NULL)
{
printf("day_data not allocated\n");
exit(0);
}
for(i = 0; i < num_stocks; i++)
if ((day_data[i] = (data*)malloc(days_of_data*sizeof(data))) == NULL)
{
printf("data[%d] not allocated\n", i);
exit(0);
}
for(i = 0; i < num_stocks; i++)
for(n = 0; n < days_of_data; n++)
if ((day_data[i][n].date = (char*)malloc(20)) == NULL)
{ printf("data[%d][%d] not allocated\n", i,n);
exit(0);
}
/* ... code omitted ... */
if ( (stocks = fopen(file_list, "r") )== NULL)
printf("didn't open file list\n");
i = 0;
while (fgets(stock, sizeof(stock), stocks) != NULL)
{
printf("%s",stock);
strcpy(fullpath,stock_dir);
strcat(fullpath,stock);
fullpath[strcspn(fullpath, "\n")] = 0;
if ( (stk = fopen(fullpath, "r") )== NULL)
printf("didn't open quote list\n");
fgets(header,sizeof(header),stk);
n=0;
while(fgets(line_of_data, sizeof(line_of_data),stk) !=NULL)
{
fgets(line_of_data,sizeof(line_of_data),stk);
day_data[i][n].date = strtok(line_of_data, ",");
sclose = strtok(NULL,",");
day_data[i][n].close = atof(sclose);
svol = strtok(NULL, ",");
day_data[i][n].vol = atoi(svol);;
printf("%s %f %d\n",day_data[i][n].date,day_data[i][n].close,day_data[i][n].vol);
n++;
}
fclose(stk);
i++;
}
for (n = look_back_period - 1; n < (days_of_data - look_back_period); n++)
printf("%d %s %f %d\n",n, day_data[1][n].date, day_data[1][n].close, day_data[1][n].vol);
}
The print statement in the while(fgets(line_of_data, sizeof(line_of_data),stk) !=NULL) loop shows that everything went into the right place. But when I print values outside they're mostly wrong. I'm supposed to add more details but I don't know what else to say. I lose the values in the struct when I leave the loop.
You overwrite the same data again and again.
Take a look at your structure:
typedef struct daily_data {
char *date; ///< a pointer without own storage
float close;
int vol;
}data;
while processing your file you read each line into line_of_data
while(fgets(line_of_data, sizeof(line_of_data),stk) !=NULL)
you tokenize the line_data and assign the pointer to data->date
day_data[i][n].date = strtok(line_of_data, ",");
What tokenize (strtok reference) does is inserting terminators into your input string and returning the pointer to the start of the new part of your input. So no new memory is allocated at this point. the returned pointer points into your input string.
So effectively you assigning the local variable pointer to your data storage structure.
Additionally to this you lose the pointer to your initially allocated memory for the date pointer.
I would suggest you to remove the a priory allocation of date and allocate the required memory at the point you really know the required length or if you are sure, you know the maximum length, then you can just make the date member an array.
So you either have to allocate new memory and copy the tokenized data or if you made date a fixed size array, just copy the tokenized data.
on the first variant it would look like this
char * tok = strtok(line_of_data, ",");
day_data[i][n].date = malloc(strlen(tok)+1);
strcpy(day_data[i][n].date, tok);
(+ remove the pre allocation of the date member)
or the second variant:
change data to
typedef struct daily_data {
char date[20];
float close;
int vol;
}data;
and the processing code looks like this:
char * tok = strtok(line_of_data, ",");
strcpy(day_data[i][n].date, tok);
(+ (of course) remove the pre allocation of the date member)
You also should in any case add error handling if the tokenized string exceeds the max length or the format of the lines does not match the expectation (missing delimiters, wrong/invalid number(formats), ...).
I am currently trying to parse a string into an array of strings.
So far, I currently believe I've succeeded in splitting up the string by inserting '\0' after every word "chunk".
However, when I attempt to free the string array later, some of my words have the same byte address, and thus, when I try to free one of them, the other gets freed as well.
This is the code for my parser, I apologize for its messy form:
/*
* parser()
*
* Parses a given string into different words and returns a list with the words.
* If there is a non-space and non-alphabetic character an error is recorded.
*/
void parser(char* str, char** actualList, char** freeingList,char* error, int* length){
// initialize variables
bool chara = false;
bool beginning = true;
int size = strlen(str);
bool nonAlphaSpace = false;
// iterate through the entire string
for(int i = 0; i < size; i++){
// if the character is not either a space or an alphabetic character
if(isspace(str[i])==0 && isalpha(str[i])==0 && !nonAlphaSpace){
*error = str[i];
nonAlphaSpace = true;
}
}
// if there was no irregular character
if(!nonAlphaSpace){
for(int j = 0; j < size; j++){
// if the character is the beginning of the current string
if(beginning){
// record this string into the list of words
freeingList[*length] = &str[j];
(*length)++;
// set the status of any alphabetic character being present to false;
chara = false;
// if the current character is an alphabetic character
if(isalpha(str[j])!=0){
chara = true;
}
beginning = false;
}
// if the character is a space
else if(isspace(str[j])!=0){
// if there was a character beforehand
if(chara){
// get the pointer to the next character
char* new = &str[j+1];
// change the current character to a null
str[j] = '\0';
// realign the pointer to the string to rest of the string
str = new;
j = -1;
size = strlen(str);
beginning = true;
}
}
// if the character is an alphabetic character
else{
chara = true;
}
}
// if the last chunk of string left didn't contain any characters
if(!chara){
free(str);
}
// for every word extracted
for(int k = 0; k < *length; k++){
int newSize = strlen(freeingList[k]);
bool first = true;
// get the pointer to the first character in the word, i.e. not the first few spaces
for(int l = 0; l < newSize; l++){
if(isspace(freeingList[k][l])==0 && first){
actualList[k] = &freeingList[k][l];
first = false;
}
}
}
}
}
This is when I attempt to free it:
// free the current collection of strings
for(int j = 0; j < size; j+=2){
free(words[j]);
}
When I input "home or for" into the parser and later try to free it, the address of "home" is 0x7fffffffe840 while the address of "for" is 0x7fffffffe848. This leads me to believe that the freeing of home also frees or causing a SIGABRT error later.
Is this assumption correct? How can I overcome this double freeing?
You should only call free() on pointers returned by malloc(), calloc(), or realloc(). What it looks like you are doing:
char *ptr = malloc(100);
char *ptr2 = &ptr[10];
free(ptr2); // You can't do that.
I suspect you meant to make a copy of the strings. Here's a simplified version:
void parser(char* str, char** actualList, int* length) {
char *start = str; // The start of the current string
int count = 0; // Number of strings copied
while (*str) {
if (isspace(*str)) {
*str = '\0';
actualList[count] = malloc(strlen(start) + 1); // Allocate space for string
strcpy(actualList[count++], start); // Copy string
start = str + 1; // Reset for next string
if (count == *length - 1) break; // Don't overflow pointer array
}
str++;
}
// Grab the final string
actualList[count] = malloc(strlen(start) + 1); // Allocate space for string
strcpy(actualList[count++], start); // Copy string
*length = count;
}
Then call it like:
char input[] = "home or for";
char *words[5];
int max_words = 5;
parser(input, words, &max_words);
// max_words should be 3 now
for (int i = 0; i < max_words; i++) {
printf("%s\n", words[i]);
}
// Clean up
for (int i = 0; i < max_words; i++) {
free(words[i]);
}
Output:
home
or
for
That's not a parser, though. More a tokenizer.
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
bool parser(char const *str, char ***words, size_t *num_words, size_t *error)
{ // ^^^ gaaaah! *)
assert(words);
errno = 0;
size_t length = strlen(str);
for (size_t i = 0; i < length; ++i) {
if (!isalnum(str[i]) && !isspace(str[i])) {
*error = i; // the position is most likely more meaningful than the character
return false; // get outta here!!
}
}
char const *begin;
char const *end;
*num_words = 0;
*words = NULL;
bool in_word = false;
for (size_t i = 0; i <= length; ++i) {
if (!in_word && isalnum(str[i])) { // word begins
begin = str + i;
in_word = true;
}
else if (in_word && !isalnum(str[i])) { // word ended
end = str + i;
char *word = calloc(end - begin + 1, sizeof *word);
if (!word) {
for (size_t i = 0; i < num_words; ++i)
free((*words)[i]);
free(*words);
errno = ENOMEM;
return false;
}
memcpy(word, begin, end - begin);
char **tmp = realloc(*words, (*num_words + 1) * sizeof *tmp);
if (!tmp) {
free(word);
for (size_t i = 0; i < num_words; ++i)
free((*words)[i]);
free(*words);
errno = ENOMEM;
return false;
}
*words = tmp;
tmp[(*num_words)++] = word;
in_word = false;
}
}
return true;
}
int main(void)
{
char const *foo = "slfkja askdfj jk j aksjf lasjdflkjsdlf jask fdjl";
char **words = NULL;
size_t num_words = 0;
size_t error = 0;
if (!parser(foo, &words, &num_words, &error)) {
if (errno == ENOMEM)
fputs("Not enough memory. :(\n\n", stderr);
else fprintf(stderr, "Error at position %zu: \"%s\"\n\n", error, foo + error);
return EXIT_FAILURE;
}
puts("List of words:");
for (size_t i = 0; i < num_words; ++i) {
printf("\"%s\"\n", words[i]);
free(words[i]);
}
free(words);
}
C should be renamed brainf*ck ...
*) Three Star Programmer
you modify the value of str in the body of the function (in the line str = new; (don't use new as an identifier, more if you plan to use this code as C++ code, as new is a reserved word in C++). As you don't call malloc(3) in the function body, it's quite normal you get a problem from free(3), as free requires to be passed a pointer previously generated with malloc (and only once, so you cannot call it twice with the same pointer). This is the reason of you getting SIGABRT and the like. As a general rule, don't call free(3) in a function you don't also call malloc for the same pointer. That use is error prone and you will run into trouble more than once a day if you insist in doing everything in a single function.
better than apologize for the messy form of the code, please, clean it before posting. Post a minimu (meaning the minimum code that shows the error), full (meaning that we can compile it and observe the result you post as failing), verifiable (code that shows it producing the observed result, and not the expected one) and complete (this means we have nothing to do but compile it and run) code (so we can test it failing as you say, without having to correct it first) That way, we can make a diagnostic of what happens in your code. If we need to correct the code just to make it runnable, we can correct the main problem you are observing and be unable to see the mistake. You see? :)
Note about using new as an identifier in C code:
Many Unit Testing frameworks require your code to be compilable as C++ code, so it can be used by the framework (at least Google Test requires this) If you plan to write unit tests for your code, remember that new is a reserved word in C++ for the operator new, and so, your code will produce syntax errors if you try to compile it with a c++ compiler. Better if you don't use it.
This is part of an assignment, so the instructions are clear and I'm not allowed to use anything other than what is specified.
The idea is simple:
1) Create an array of structs which hold a string and a count
2) Count the occurrence of the string in each struct and store the count in that struct
3) Print the strings and their number of occurrences
I have been explicitly told to use the fgets and strstr functions
Here is what I've got so far,
#define MAX_STRINGS 50
#define LINE_MAX_CHARS 1000
int main(){
int n = argc - 1;
if (n > MAX_STRINGS) {
n = MAX_STRINGS;
}
Entry entries[MAX_STRINGS];
char **strings = argv+1;
prepare_table(n, strings, entries);
count_occurrences(n, stdin, entries);
print_occurrences(n, entries);
}
void prepare_table (int n, char **strings, Entry *entries) {
// n = number of words to find
// entries = array of Entry structs
for (int i = 0; i < n; i++){
Entry newEntry;
newEntry.string = *(strings + 1);
newEntry.count = 0;
*(entries + i) = newEntry;
}
}
void print_occurrences (int n, Entry *entries) {
for (int i = 0; i < n; i++){
printf("%s: %d\n", (*(entries + i)).string, (*(entries + i)).count);
}
}
void count_occurrences (int n, FILE *file, Entry *entries) {
char *str;
while (fgets(str, LINE_MAX_CHARS, file) != NULL){
for (int i = 0; i < n; i++){ // for each word
char *found;
found = (strstr(str, (*(entries + i)).string)); // search line
if (found != NULL){ // if word found in line
str = found + 1; // move string pointer forward for next iteration
i--; // to look for same word in the rest of the line
(*(entries + i)).count = (*(entries + i)).count + 1; // increment occurrences of word
}
}
}
}
I know for a fact that my prepare_table and print_occurrences functions are working perfectly. However, the problem is with the count_occurrences function.
I've been given a test file to run which just tells me that I'm not producing the correct output. I can't actually see the output to figure out whats wrong
I'm new to pointers, so I'm expecting this to be a simple error on my part. Where is my program going wrong?
fgets(char * restrict str, int size, FILE * restrict stream) writes into the buffer at str... but you don't have a buffer at str. What is str? It's just a pointer. What's it pointing at? Garbage, because you haven't initialized it to something. So it might work or it might not (edit: by which I mean you should expect it not to work, and be surprised if it did, thank you commenters!).
You could fix that by allocating some memory first:
char *str = malloc(LINE_MAX_CHARS);
// do your stuff
free(str);
str = NULL;
Or even statically allocating:
char str[LINE_MAX_CHARS];
That's one problem I can see anyway. You say you don't have output, but surely you can add some debug statements using fprintf(stderr, "") at the very least..?
// Struct for Country Data
typedef struct
{
char name[50]; // Country name
char code[3]; // Country code
int population; // Country Population
double lifeExp; // Country Life expectancy
} CountryData;
// Struct for Dir File
typedef struct
{
char code[3];
int offSet;
} DirData;
// Function Declarations
void fillCountryStructs(CountryData ** dataPtr, int nLines, int fd);
void fillDirectoryStructs(CountryData **dataPtr, DirData **director, int nLines,int fd2);
void sortStructs(DirData **director, int nLines);
int verifyString(char *s1, char *s2);
// Main Function
// - This function starts the program, get the number of lines as a
// parameter, fills the structs and writes the data to the Country
// File and the Directory file.
int main(int argc, char *argv[]) // Always remember to pass an argument while executing
{
// Some variables
int nLines; // The number of lines
char *pEnd; // For String functions
FILE *Fin,*Fout; // File pointers
int fd;
int fd2;
nLines = strtod(argv[1], &pEnd);
CountryData **countryDataPtr; // Array of structs
CountryData **tempStruct;
DirData **director;
// Allocate memory for the struct pointers
countryDataPtr = calloc(nLines, sizeof(CountryData*));
director = calloc(nLines, sizeof(DirData*));
// File Stream for "AllCountries.dat"
if((fd = open("AllCountries.dat", O_RDWR)) ==-1)
err_sys("File not found...\n");
// File Stream for "RandomStruct.bin"
if ((fd2 = open("RandomStruct.bin", O_RDWR)) == -1)
err_sys("Failed to open binary\n");
// Filling the Country stucts
fillCountryStructs(countryDataPtr, nLines, fd);
close (fd);
//fclose(Fin); // Closing the file "AllCountries.dat"
// Writing Binary File
write(fd2, (countryDataPtr[0]->name[0]), sizeof(CountryData));
close (fd2);
//fclose(Fout);
printf("RandomStruct.bin written Sucessfully\n");
// Filling the Directory File
// File Stream for "RandomStructDir.dir"
if ((fd2 = open("RandomStructDir.dir",O_RDWR|O_TRUNC)) != -1)
err_sys("Failed to open binary\n");
fillDirectoryStructs(countryDataPtr, director, nLines, fd2);
sortStructs(director, nLines); // Sorting the structs
// Write the number of lines in the FIRST LINE
// of the Directory File
write(fd2, nLines, sizeof(nLines));
// Writing Directory File after the number of lines was written
write(fd2,(director[0]->code[0]), sizeof(DirData));
close (fd2);
//fclose(Fout);
printf("RandomStructDir.dir written Sucessfully\n\n");
exit(0);
}
// Filling the Country structs
// - This function extracts the data from the file using strtok
// and fills all the structs with their corresponding values.
void fillCountryStructs(CountryData **dataPtr, int nLines, int fd)
{
int curLine = 0; // Current line
int index = 0; // The index
char buf[BUFSIZE]; // The Buffer with the size of BUFSIZE
char *tok; // Token
char *pEnd; // For the String functions
char ch = 'a'; // The temp character
int temPop;
double temLifeExp;
int num=0;
for(curLine = 0; curLine < nLines; curLine++)
{
// Reading each line
dataPtr[curLine] = (CountryData *)calloc(1, sizeof(CountryData));
index = 0;
do
{
read(fd, &ch, 1);
buf[index++] = ch;
}
while(ch != '\n');
// Strtoking...
tok = strtok(buf, ",\n");
index = 1;
while(tok != NULL)
{
tok = strtok(NULL, ",\n");
// Get the Country Code
if(index == 1)
{
strcpy(dataPtr[curLine]->code, tok); // Copying code to the struct
}
// Get the Country Name
if(index == 2)
{
strcpy(dataPtr[curLine]->name, tok); // Copying name to the struct
}
// Get the Country Population
if(index == 7)
{
temPop = (int)strtol(tok, &pEnd, 10);
dataPtr[curLine]->population = temPop; // Copying population to the struct
}
// Get the Country Life expectancy
if(index == 8)
{
num=countchar(tok);
printf ("The number of characters entered is %d\n", num);
printf ("The character entered is %s\n",tok);
temLifeExp = strtod(tok, &pEnd);
dataPtr[curLine]->lifeExp = temLifeExp; // Copying life expectancy to the struct
}
index++;
}
}
}
int countchar (char list[])
{
int i, count = 0;
for (i = 0; list[i] != '\0'; i++)
count++;
return (count);
}
// Filling the Directory Structs
// - This function fills the directory with the offset
void fillDirectoryStructs(CountryData **dataPtr, DirData **director, int nLines, int fd2)
{
int i = 0;
for(i = 0; i < nLines; i++)
{
strcpy(director[i]->code, dataPtr[i]->code); //It crashes in this Line
director[i]->offSet = sizeof(CountryData) * (i);
}
}
// Sorting the Dir Structs
// - This function sorts the Directory Structs.
void sortStructs(DirData **director, int nLines)
{
int maxNumber;
int i;
DirData **temp;
temp = calloc(1, sizeof(DirData));
// Sorting the array of pointers!
for(maxNumber = nLines - 1; maxNumber > 0; maxNumber--)
{
for(i = 0; i < maxNumber; i++)
{
if((verifyString(director[i]->code, director[i+1]->code)) == 1)
{
temp[0] = director[i];
director[i] = director[i+1];
director[i+1] = temp[0];
}
}
}
}
// Veryfying the strings
// - This function compares two strings and return a specific value
// accordingly.
int verifyString(char *s1, char *s2)
{
int i;
if(strcmp(s1,s2) == 0)
return(0); // They are equal
for(i = 0; s1[i] != 0; i++)
{
if(s1[i] > s2[i])
return(1); // s1 is greater
else if(s1[i] < s2[i])
return(2); // s2 is greater
}
return (2); // s2 is greater
}
So I get segmentation fault and I have no Idea why? maybe is something about the pointers. I specified where it crashes (void fillDirectoryStructs) that method the first line.
When I compile I get :
Countries.c: In function 'main':
Countries.c:68: warning: passing argument 2 of 'write' makes pointer from integer without a cast
Countries.c:84: warning: passing argument 2 of 'write' makes pointer from integer without a cast
Countries.c:86: warning: passing argument 2 of 'write' makes pointer from integer without a cast
Countries.c:232:2: warning: no newline at end of file
I don't know a lot about pointers but I have to use system calls, so I can't use any of the FILE * functions (fwrite(), etc) that is why I'm using plain write() and read().
When I run it I get segmentation fault when It gets to that point I just specified.
for test purposes I'm trying to print this
printf("test: %s\n", countryDataPtr[0]->code[0]);
instead of writing and it crashes there, why? what am I doing wrong? shouldn't that get the code of that first country in my struct? thanks
Well, you need to listen to your compiler and take its warnings seriously.
This:
write(fd2, nLines, sizeof(nLines));
is wrong, and would explain the warning. The variable nLines has type int, but if you look at the [documentation for write()] you can see that the 2nd argument has type void *.
So it will interpret your integer value as a pointer, and start reading memory which you have no right to be reading.
You need:
write(fd2, &nLines, sizeof nLines);
Note that sizeof is not a function, it only needs parenthesis when the argument is a type name (since it then needs a cast expression to the type in question, and casts are writen as a type name enclosed in parenthesis).
Also, you need to be prepared for the reality that I/O can fail. The write() function has a return value which you should be checking.
There are a number of other problems with your code, in addition to the serious one unwind pointed out.
This:
CountryData **countryDataPtr; // Array of structs
is not an Array of structs. Once allocated, it could be an array of pointers to structs.
This:
write(fd2, (countryDataPtr[0]->name[0]), sizeof(CountryData));
does not write one CountryData instance (much less a whole array of them). It takes the integer value of the first character of the first element's name, and treats it as a pointer just like you do with nLines.
If you want to write the first element, it would look like this:
write(fd2, countryDataPtr[0], sizeof(CountryData));
and if you wanted to write all the elements, you'd either need a loop, or a contiguous array of structs you can write in one go.