I have a problem with this part of my code, I'm trying to read the lines of a file and cut only the first word of each line and then save it in an array.
Example:
two roads diverged in a yellow wood
and sorry i could not travel both
and be one traveler long i stood
and looked down one as far as i could
to where it bent in the undergrowth
and as a result I expect a vector like this: "two, and, and, and, to"
but I get this: "to, to, to, to, to".
My code
dictionary *load_word(int autor, dictionary *D_first)
{
FILE *date;
char line[LONG_MAX_LINE];
char exeption[4] = " \n\t";
char *word;
int j=0;
if (autor == 1)
{
if ((date = fopen("test.txt", "r")) == NULL)
{
perror("robert_frost.txt");
}
while (fgets(line, LONG_MAX_LINE, date ) != NULL)
{
word = strtok(line, exeption); /*first word*/
add_dictionary_first(D_first, j, word);
j++;
}
fclose(date);
}
return D_first;
}
void add_dictionary_first(dictionary *D, int cont, const char *value)
{
expand_dictionary(&D, 1);
D->Distribution[D->size-1]->cont = cont;
D->Distribution[D->size-1]->value = value;
}
The problem lies within this line (as Vlad from Moscow posted in the comments):
D->Distribution[D->size-1]->value = value;
This is just pointer assignment. That's not wrong per se, but depending on the
context, it is not what you want.
while (fgets(line, LONG_MAX_LINE, date ) != NULL)
{
word = strtok(line, exeption); /*first word*/
add_dictionary_first(D_first, j, word);
...
}
Here you call add_dictionary_first always with the same variable line. It is
an array but arrays decay into pointers when passing them as arguments to
functions. That means that all your D->Distribution[D->size-1]->value point to
the same location. The last line in your input file begins with to and that's why you get only
to.
You need to copy the string with strcpy.
man strcpy
#include <string.h>
char *strcpy(char *dest, const char *src);
The strcpy() function copies the string pointed to by src, including the terminating
null byte ('\0'), to the buffer pointed to by
dest. The strings may not overlap, and the destination string dest must be
large enough to receive the copy.
Because you haven't posted the structure I can only guess that value is
declared as char* (if it were char[] the compiler would have complained).
Option 1
D->Distribution[D->size-1]->value = malloc(strlen(value) + 1); // note the +1 here
if(D->Distribution[D->size-1]->value == NULL)
{
// error handling
}
strcpy(D->Distribution[D->size-1]->value, value);
Option 2
If strdup is available in your system
D->Distribution[D->size-1]->value = strdup(value);
if(D->Distribution[D->size-1]->value == NULL)
{
// error handling
}
In either case you would have to free the memory later.
Related
I have some code here where, given a .txt file whose contents is
find replace pre
pre
cpre
,I want to find every instance of "pre", and append "k" to it. ie the file should become "find replace kpre".
So I first set out to create a string that is the concatenation of k and pre
(assume k and pre are argv[1] and argv[3], respectively)
char appended[1024];
strcpy(appended, argv[1]);
strcat(appended, argv[3]);
printf("appended string is %s", appended); //prints kpre, which is good
char *replaced = replace(buf, argv[3], appended);
//*string is a line in the file
char* replace(char *string, char *find, char *replace) {
char *position;
char temp[1024];
int find_length = strlen(find);
int index = 0;
while ((position = strstr(string, find)) != NULL) {
strcpy(temp, string);
index = position - string;
string[index] = '\0';
strcat(string, replace); //add new word to the string
strcat(string, temp + index + find_length); //add the unsearched
//remainder of the string
}
return string;
}
.................
fputs(replaced, temp);
Checking on the console, appended = "kpre", which is correct, but when the code is run the file looks like
find replace kkkkkkkkkkkkkkkk.....kkkkkkk
kkkkkkkkk......kkkkk
ckkkkk....kkkkk
the k's go on for a while, I cannot see pre when scrolling all the way to the right. I'm having difficulty figuring out why the code doesn't replace
the instance of 'pre' with 'kpre', even when the appended variable appears to be correct. I have a feeling it has to do with the fact that I set a 1024 character for temp, but even then I'm not sure why k was copied so many times.
Here
while ((position = strstr(string, find)) != NULL) {
you are passing string to strstr() function. The strstr() will return the pointer to the first occurrence of find in string. When you replace pre with kpre and calling again strstr(), it is retuning the pointer to the first occurrence of pre in string which is a sub string of replace string. After some iterations of while loop, it will start accessing the string beyond its size which will lead to undefined behavior.
Instead of passing string to strstr(), you should pass pointer to string and after every replace operation, the make the pointer point to after the replaced part of string. Other way is you can traverse the string character by character using pointer instead of using strstr(), like this:
#define BUFSZ 1024
char* replace(char *string, const char *find, const char *replace) {
if ((string == NULL) || (find == NULL) || (replace == NULL)) {
printf ("Invalid argument..\n");
return NULL;
}
char temp[BUFSZ];
char *ptr = string;
size_t find_len = strlen(find);
size_t repl_len = strlen(replace);
while (ptr[0]) {
if (strncmp (ptr, find, find_len)) {
ptr++;
continue;
}
strcpy (temp, ptr + find_len); // No need to copy whole string to temp
snprintf (ptr, BUFSZ - (ptr - string), "%s%s", replace, temp);
ptr = ptr + repl_len;
}
return string;
}
Note that above code is based on the example you have posted in your question and just to give you an idea about how you can achieve your goal without using strstr(). When writing code, take care of the other possibilities as well like, replace is a huge string.
Here is my full code, it looks like to work, but it's not working very well.
I would accept any code, that is working like this.
Firstly, the code works, but when I want to add the third name to the struct, it crashes.
Is there any other way to do this?
I need struct, because in the future, I want to add some other params, like age, average, gender, etc.
Please, help me out.
//The student table
typedef struct students {
char name[50];
} students;
//Global params
int scount = 0;
students *s;
//Basic functions
void addNewStudent();
int main()
{
int loop = 1;
char in;
int ch;
printf("Willkommen.\n Wahlen Sie bitte von die folgenden Optionen:\n");
while (loop)
{
printf("\t[1] Neue Student eingeben\n");
printf("\t[9] Programm beenden\n");
scanf(" %c", &in);
while ((ch = getchar()) != '\n');
switch (in)
{
case '1':
addNewStudent();
break;
case '9':
loop = 0;
break;
default: printf("------\nOption nicht gefunden.\n------\n");
break;
}
}
free(s);
return 0;
}
void addNewStudent()
{
int index = 0;
if (scount == 0)
{
s = (students*)malloc(sizeof(students));
}
else
{
realloc(s, sizeof(students) * scount);
}
printf("Geben Sie Bitte die Name:\n");
fgets(s[scount].name, sizeof(s[scount].name), stdin);
while (s[scount].name[index] != '\n')
{
index++;
}
s[scount].name[index] = '\0';
scount++;
}
I'm using Visual Studio.
Thanks for help!
students *mynew= realloc(s, sizeof(students)* (scount+1));
if( mynew != NULL )
s=mynew;
Otehrwise you are having a memory leak. You didn't use the return value of realloc.
Don't cast the return type of malloc.
As per standard §7.22.2.35
void *realloc(void *ptr, size_t size)
The realloc function deallocates the old object pointed to by ptr and
returns a pointer to a new object that has the size specified by size.
It is good not to use the same pointer variable on which you are calling malloc because in case it fails you will lose reference to the old one too (unless it is stored by other means).
Also you didn't check the return value of malloc.
s = malloc(sizeof(students));
if( s == NULL ){
frpntf(stderr,"%s","Memory allocation failed");
exit(1);
}
Also you should check the return value of fgets().
if( fgets(s[scount].name, sizeof(s[scount].name), stdin) == NULL){
fprintf(stderr,"%s","Error in input");
exit(1);
}
Also trying to compile your code it showed this
warning: ignoring return value of ‘realloc’, declared with attribute warn_unused_result [-Wunused-result]
realloc(s, sizeof(students) * scount);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When compiling try not to ignore any warning messages. It showed the problem you had.
Important point: (why scount+1 in realloc?)
When reallocating the general idea is increase the number of students. And for that you need to have extra memory allocated for an student. That's why the scount+1 in the code.(realloc).
Some other points:
while (s[scount].name[index] != '\n')
{
index++;
}
s[scount].name[index] = '\0';
You can do it like this also
size_t len = strlen(s[scount].name);
if(len){
s[scount].name[len-1]='\0';
}
To understand why from standard §7.21.7.2
char *fgets(char * restrict s, int n,FILE * restrict stream)
The fgets function reads at most one less than the number of
characters specified by n from the stream pointed to by stream into
the array pointed to by s. No additional characters are read after a
new-line character (which is retained) or after end-of-file. A null
character is written immediately after the last character read into
the array.
\0 character was there already in the inputted string. You can get the length of it but you know that the one before the \0 is the \n character 1 that you entered by pressing the Enter key. We are overwriting it with the \0.
1. This is the usual case but not the only one. There are two cases where this might not be the right way to look at the thing.
The input line has n-1 or more characters before the '\n'. The the one before \0 will not be the \n rather it will be some character inputted by the user.
The last line is a stream which may not have a '\n'. (stdin closed). In that case also the input doesn't contain the \n.
So in these cases the idea of removing \n would fail.Discussed in comment. (chux)
A better and safe solution than overwriting this way:
s[scount].name[strcspn(s[scount].name, "\n")] = '\0';
The explanation from the link is that if a \0 is given as input then we will basically write to s[scount].name[SIZE_MAX] which is not desired.
From the standard §7.24.5.3
size_t strcspn(const char *s1, const char *s2)
The strcspn function computes the length of the maximum initial
segment of the string pointed to by s1 which consists entirely of
characters not from the string pointed to by s2.
How to correctly malloc a struct in C ?
p = malloc(sizeof *p);
if (p == NULL) Handle_OutOfMemory();
How to correctly re-allocate a struct in C ?
void *t = realloc(p, sizeof *p * number_of_elements);
if (t == NULL && number_of_elements > 0) {
Handle_OutOfMemory();
} else {
p = t;
}
p points to some struct. Notice no coding of that type in above.
OP' primary problem is not using the return value of realloc() and allocating 1-too-small
// realloc(s, sizeof(students) * scount);
s = realloc(s, sizeof *s * (scount+1)); // or use above code with check for out-of-memory.
realloc returns a new pointer that you need to keep:
students* snew = realloc(s, sizeof(students) * (scount + 1));
if (!snew) {
free(s); // If there is not enough memory, the old memory block is not freed
// handle out of memory
} else {
s = snew;
}
You are not allocating it back! Take a look at how realloc works. You need to assign the pointer back after making the re-allocation like this.
if (scount == 0)
{
s = (students*)malloc(sizeof(students));
}
else
{
students *temp = realloc(s, sizeof(students) * (scount+1));
if(temp == NULL){
free(s);
}
else{
s = temp;
}
}
By Definition, realloc returns a void pointer but you aren't collecting it.
void *realloc(void *ptr, size_t size);
realloc returns a NULL if there's not enough space. So you can re-assign it when you are sure that it is not NULL
Just make a small change above and your code works like a charm!
Cheers!
Ok so I have the below code and I am just pulling various things from a file and inputing them in an array of structs, it "seemingly" works initially, BUT when I go to printing it after it is done with the file it seemed to have replaced all of the courses and names with the very last vale, oddly this doesnt happen with the integers (grades), the grades do get inputed properly.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student {
char *name;
char *course;
int grade;
};
void courseSort(struct student d[20], int size);
int main(void)
{
FILE* fp;
char* filename = "grades.csv";
char buffer[100];
char* name, *class;
char* del=",";
int grade, i, counter=0;
struct student d[20];
if((fp=fopen(filename, "r"))==NULL)
{
printf("unable to open %s\n", filename);
exit(1);
}
while(fgets(buffer, sizeof(buffer), fp) !=NULL)
{
name = strtok(buffer,del);
class=strtok(NULL,del);
grade = atoi(strtok(NULL,del));
d[counter].name=name;
d[counter].course=class;
d[counter].grade=grade;
printf("%s %s %d\n",d[counter].name,d[counter].course,d[counter].grade);
counter++;
}
printf("\n\n\n");
for(i=0;i<counter;i++)
printf("%s %s %d\n",d[i].name,d[i].course,d[i].grade);
courseSort(d,counter);
fclose(fp);
}
I am not sure what I am doing wrong :( it all seems straightforward but not sure why it just replaces everything with the latest one.
The strtok returns a pointer to the buffer and does not allocate memory. Since you do not copy the strings, you end up with lots of pointers pointing to the same buffer that is overwritten at each iteration of the loop.
To fix this, you need to change your loop to copy the strings using strdup:
while(fgets(buffer, sizeof(buffer), fp) != NULL)
{
d[counter].name = strdup(strtok(buffer, del));
d[counter].course = strdup(strtok(NULL, del));
d[counter].grade = atoi(strtok(NULL, del));
counter++;
}
Don't forget to return the allocated memory with free once you no longer need the strings:
for (i = 0; i < counter; i++) {
free(d[i].name);
free(d[i].course);
d[i].name = NULL;
d[i].course = NULL;
}
Note that strdup is part of POSIX1.2001 standard, not part of C89. If it is not available, you'll have to re-implement it yourself (quite easy):
char *my_strdup(const char *str) {
char *copy;
size_t len = strlen(str) + 1;
if (len == 0) return NULL;
copy = (char *)malloc(len);
if (copy == NULL) return NULL;
memcpy(copy, str, len);
return copy;
}
This is a simple misunderstanding of pointers and char arrays (strings). Here are a couple pages that explains them pretty well:
http://www.cplusplus.com/doc/tutorial/pointers/
http://www.cplusplus.com/doc/tutorial/ntcs/
In your case, you are setting your struct pointer values equal to the returned pointer from strtok. A lot of those string functions work by putting the result at a certain memory address and returning the pointer to it. The pointer returned is always the same, so all your struct values are going to point to the last result of the strtok call.
This is why you need strdup (String duplicate). Basically it takes the value at the address given and copies the contents into a new place in memory and returns the value.
The error is here.
d[counter].name=name;
replace with:
d[counter].name = strdup(name); /*don't forget to free this memory.*/
the issue for the courses is the same.
I have this piece of code outside the main function
mystr * arrstr[] = {
"rec",
"cent",
"ece",
"ce",
"recent",
"nt",
};
I modified it so that it can read the values from a text file. for this purpose i modified this working code to read line from file into array named string.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
int i=0,j;
char* string[100];
char line[100];
FILE *file;
file = fopen("patt", "r");
while(fgets(line, sizeof(line), file)!=NULL) {
printf("%s", line);
string[i] = (char*)malloc(strlen(line));
strcpy(string[i], line);
i++;
}
fclose(file);
return 0;
}
so the final code is now something like this.
..
..
char *getpatterns(const char *filename) {
int i=0;
char* string[100];
char line[100];
FILE *file;
file = fopen(filename, "r");
while(fgets(line, sizeof(line), file)!=NULL) {
//printf("%s", line);
string[i] = (char*)malloc(strlen(line));
strcpy(string[i], line);
i++;
}
fclose(file);
return(string);
}
mystr * arrstr[] = getpatterns("patt");/*{
"rec",
"cent",
"ece",
"ce",
"recent",
"nt",
};*/
..
..
But i get errors like this.
example1.c: In function ‘getpatterns’:
example1.c:43:2: warning: return from incompatible pointer type [enabled by default]
example1.c:43:2: warning: function returns address of local variable [enabled by default]
example1.c: At top level:
example1.c:45:1: error: invalid initializer
make: *** [example1.o] Error 1
Here line 45 is this line
mystr * arrstr[] = getpatterns("patt");/*{
Please suggest corrective action.
The first warnings are that you are trying to return a char ** as a char * (which is not a good idea), and that you are returning a local variable which is deallocated when the function returns (also not a good idea). The last is telling you that you can't use function calls in initializers of global variables in C (you can do some of that in C++, though I'm not convinced you can do this one).
Fixing it will take some rethinking. You need the function to return allocated memory, or you need to pass the memory to the function. And you'll have to change the type of the global variable. And you'll need to know how many entries there are in the array, somehow.
mystr **arrstr = 0; // Either
mystr *arrstr[100]; // Or
On the whole, I'd probably go with memory allocation and the 'either' declaration:
mystr **arrstr = 0;
char **getpatterns(const char *file)
{
char **array = 0;
...code similar to yours that allocates entries in the array...
...include space for a null pointer to mark the end of the list of strings...
return(array);
}
int main(void)
{
arrstr = getpatterns("patt");
...
}
(Another 'cheat' mechanism would use static char *string[100]; in getpatterns(); you still have to fix the return type and the type of the global variable.)
I tried these but, errors were not resolved: ...
It's impossible to tell exactly what was wrong without your code. However, the code below works for me. The source code was in a file gp.c; the source code prints itself, and releases the memory. Checked under valgrind with a clean bill of health.
Note that your original code did not allocate enough space for the strings it was copying (because you retained the newline read by fgets() — but you were at least using fgets() and not gets(), which is very important). This code uses memmove() — it could use memcpy() instead since there's guaranteed to be no overlap, but memmove() always works and memcpy() doesn't necessarily work when the source data overlaps the target data. It knows how long the string is, so the copy function doesn't need to test for whether the character being copied is a NUL '\0'. The code carefully ensures that there's a null pointer at the end of the list of pointers; that's how you know when you've reached the end of the list of strings. The code also works when gp.c is an empty file.
The algorithm using three items num_xxx, max_xxx, and xxx is a typical way to handle incremental allocation. It typically over-allocates slightly; if you're concerned about the space, you could use strings = realloc(strings, (num_strings+1) * sizeof(*strings)); max_strings = num_strings + 1; at the end of the loop to release the extra space. The + 1 is to allow for the null pointer. By roughly doubling the size allocated each time you allocate, you avoid quadratic behaviour compared with incrementing by one each time.
Notice too that the code carefully avoids losing the allocated space if the realloc() fails. You should 'never' use space = realloc(space, new_size); to avoid losing your pointer. The code carefully avoids dereferencing null pointers, and simply stops reading when there is a memory shortage.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern char **getpatterns(const char *filename);
char **getpatterns(const char *filename)
{
size_t num_strings = 0;
size_t max_strings = 0;
char **strings = 0;
FILE *file = fopen(filename, "r");
if (file != 0)
{
char line[4096];
while (fgets(line, sizeof(line), file) != NULL)
{
if (max_strings == 0 || num_strings >= max_strings - 1)
{
size_t new_num = max_strings * 2 + 2;
char **new_space = realloc(strings, new_num * sizeof(*new_space));
if (new_space == 0)
break;
strings = new_space;
max_strings = new_num;
}
size_t len = strlen(line); /* Includes '\n' at end */
strings[num_strings] = (char*)malloc(len);
memmove(strings[num_strings], line, len - 1);
strings[num_strings][len] = '\0';
strings[++num_strings] = 0; /* Null terminate list of strings */
}
fclose(file);
}
return(strings);
}
int main(void)
{
char **data = getpatterns("gp.c");
char **argp = data;
if (argp != 0)
{
/* Print data */
while (*argp != 0)
puts(*argp++);
/* Free space */
argp = data;
while (*argp != 0)
free(*argp++);
free(data);
}
return(0);
}
I have lineget function that returns char *(it detects '\n') and NULL on EOF.
In main() I'm trying to recognize particular words from that line.
I used strtok:
int main(int argc, char **argv)
{
char *line, *ptr;
FILE *infile;
FILE *outfile;
char **helper = NULL;
int strtoks = 0;
void *temp;
infile=fopen(argv[1],"r");
outfile=fopen(argv[2],"w");
while(((line=readline(infile))!=NULL))
{
ptr = strtok(line, " ");
temp = realloc(helper, (strtoks)*sizeof(char *));
if(temp == NULL) {
printf("Bad alloc error\n");
free(helper);
return 0;
} else {
helper=temp;
}
while (ptr != NULL) {
strtoks++;
fputs(ptr, outfile);
fputc(' ', outfile);
ptr = strtok(NULL, " ");
helper[strtoks-1] = ptr;
}
/*fputs(line, outfile);*/
free(line);
}
fclose(infile);
fclose(outfile);
return 0;
}
Now I have no idea how to put every of tokenized words into an array (I created char ** helper for that purpose), so that it can be used in qsort like qsort(helper, strtoks, sizeof(char*), compare_string);.
Ad. 2 Even if it would work - I don't know how to clear that line, and proceed to sorting next one. How to do that?
I even crashed valgrind (with the code presented above) -> "valgrind: the 'impossible' happened:
Killed by fatal signal"
Where is the mistake ?
The most obvious problem (there may be others) is that you're reallocating helper to the value of strtoks at the beginning of the line, but then incrementing strtoks and adding to the array at higher values of strtoks. For instance, on the first line, strtoks is 0, so temp = realloc(helper, (strtoks)*sizeof(char *)); leaves helper as NULL, but then you try to add every word on that line to the helper array.
I'd suggest an entirely different approach which is conceptually simpler:
char buf[1000]; // or big enough to be bigger than any word you'll encounter
char ** helper;
int i, numwords;
while(!feof(infile)) { // most general way of testing if EOF is reached, since EOF
// is just a macro and may not be machine-independent.
for(i = 0; (ch = fgetc(infile)) != ' ' && ch != '\n'; i++) {
// get chars one at a time until we hit a space or a newline
buf[i] = ch; // add char to buffer
}
buf[i + 1] = '\0' // terminate with null byte
helper = realloc(++numwords * sizeof(char *)); // expand helper to fit one more word
helper[numwords - 1] = strdup(buffer) // copy current contents of buffer to the just-created element of helper
}
I haven't tested this so let me know if it's not correct or there's anything you don't understand. I've left out the opening and closing of files and the freeing at the end (remember you have to free every element of helper before you free helper itself).
As you can see in strtok's prototype:
char * strtok ( char * str, const char * delimiters );
...str is not const. What strtok actually does is replace found delimiters by null bytes (\0) into your str and return a pointer to the beginning of the token.
Per example:
char in[] = "foo bar baz";
char *toks[3];
toks[0] = strtok(in, " ");
toks[1] = strtok(NULL, " ");
toks[2] = strtok(NULL, " ");
printf("%p %s\n%p %s\n%p %s\n", toks[0], toks[0], toks[1], toks[1],
toks[2], toks[2]);
printf("%p %s\n%p %s\n%p %s\n", &in[0], &in[0], &in[4], &in[4],
&in[8], &in[8]);
Now look at the results:
0x7fffd537e870 foo
0x7fffd537e874 bar
0x7fffd537e878 baz
0x7fffd537e870 foo
0x7fffd537e874 bar
0x7fffd537e878 baz
As you can see, toks[1] and &in[4] point to the same location: the original str has been modified, and in reality all tokens in toks point to somewhere in str.
In your case your problem is that you free line:
free(line);
...invalidating all your pointers in helper. If you (or qsort) try to access helper[0] after freeing line, you end up accessing freed memory.
You should copy the tokens instead, e.g.:
ptr = strtok(NULL, " ");
helper[strtoks-1] = malloc(strlen(ptr) + 1);
strcpy(helper[strtoks-1], ptr);
Obviously, you will need to free each element of helper afterwards (in addition to helper itself).
You should be getting a 'Bad alloc' error because:
char **helper = NULL;
int strtoks = 0;
...
while ((line = readline(infile)) != NULL) /* Fewer, but sufficient, parentheses */
{
ptr = strtok(line, " ");
temp = realloc(helper, (strtoks)*sizeof(char *));
if (temp == NULL) {
printf("Bad alloc error\n");
free(helper);
return 0;
}
This is because the value of strtoks is zero, so you are asking realloc() to free the memory pointed at by helper (which was itself a null pointer). One outside chance is that your library crashes on realloc(0, 0), which it shouldn't but it is a curious edge case that might have been overlooked. The other possibility is that realloc(0, 0) returns a non-null pointer to 0 bytes of data which you are not allowed to dereference. When your code dereferences it, it crashes. Both returning NULL and returning non-NULL are allowed by the C standard; don't write code that crashes regardless of which behaviour realloc() shows. (If your implementation of realloc() does not return a non-NULL pointer for realloc(0, 0), then I'm suspicious that you aren't showing us exactly the code that managed to crash valgrind (which is a fair achievement — congratulations) because you aren't seeing the program terminate under control as it should if realloc(0, 0) returns NULL.)
You should be able to avoid that problem if you use:
temp = realloc(helper, (strtoks+1) * sizeof(char *));
Don't forget to increment strtoks itself at some point.