I need to read input from a file, then split the word in capitals from it's definition. My trouble being that I need multiple lines from the file to be in one variable to pass it to another function.
The file I want to read from looks like this
ACHROMATIC. An optical term applied to those telescopes in which
aberration of the rays of light, and the colours dependent thereon, are
partially corrected. (See APLANATIC.)
ACHRONICAL. An ancient term, signifying the rising of the heavenly
bodies at sunset, or setting at sunrise.
ACROSS THE TIDE. A ship riding across tide, with the wind in the
direction of the tide, would tend to leeward of her anchor; but with a
weather tide, or that running against the wind, if the tide be strong,
would tend to windward. A ship under sail should prefer the tack that
stems the tide, with the wind across the stream, when the anchor is
let go.
Right now my code splits the word from the rest, but I'm having difficulty getting the rest of the input into one variable.
while(fgets(line, sizeof(line), mFile) != NULL){
if (strlen(line) != 2){
if (isupper(line[0]) && isupper(line[1])){
word = strtok(line, ".");
temp = strtok(NULL, "\n");
len = strlen(temp);
for (i=0; i < len; i++){
*(defn+i) = *(temp+i);
}
printf("Word: %s\n", word);
}
else{
temp = strtok(line, "\n");
for (i=len; i < strlen(temp) + len; i++);
*(defn+i) = *(temp+i-len);
len = len + strlen(temp);
//printf(" %s\n", temp);
}
}
else{
len = 0;
printf("%s\n", defn);
index = 0;
}
}
like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
//another function
void func(char *word, char *defs){
printf("<%s>\n", word);
if(defs){
printf("%s", defs);
}
}
int main(void){
char buffer[4096], *curr = buffer;
size_t len, buf_size = sizeof buffer;
FILE *fp = fopen("dic.txt", "r");
while(fgets(curr, buf_size, fp)){
//check definition line
if(*curr == '\n' || !isupper(*curr)){
continue;//printf("invalid format\n");
}
len = strlen(curr);
curr += len;
buf_size -= len;
//read rest line
while(1){
curr = fgets(curr, buf_size, fp);
if(!curr || *curr == '\n'){//upto EOF or blank line
char *word, *defs;
char *p = strchr(buffer, '.');
if(p)
*p++ = 0;
word = buffer;
defs = p;
func(word, defs);
break;
}
len = strlen(curr);
curr += len;
buf_size -= len;
assert(buf_size >= 2 || (fprintf(stderr, "small buffer\n"), 0));
}
curr = buffer;
buf_size = sizeof buffer;
}
fclose(fp);
return 0;
}
It appears you need to first pull a string of uppercase letters from the beginning of the line, up to the first period, then concatenate the remainder of that line with subsequent lines until a blank line is found. Lather, rinse, repeat as needed.
While this task would be MUCH easier in Perl, if you need to do it in C, for one thing I recommend using the built-in string functions instead of constructing your own for-loops to copy the data. Perhaps something like the following:
while(fgets(line, sizeof(line), mFile) != NULL) {
if (strlen(line) > 2) {
if (isupper(line[0]) && isupper(line[1])) {
word = strtok(line, ".");
strcpy(defn,strtok(NULL, "\n"));
printf("Word: %s\n", word);
} else {
strcat(defn,strtok(line, "\n"));
}
} else {
printf("%s\n", defn);
defn[0] = 0;
}
}
When I put this in a properly structured C program, with appropriate include files, it works fine. I personally would have approached the problem differently, but hopefully this gets you going.
There are several areas that can be addressed. Given your example input and description, it appears your goal is to develop a function that will read and separate each word (or phrase) and associated definition, return a pointer to the collection of words/definitions, while also updating a pointer to the number of words and definitions read so that number is available back in the calling function (main here).
While your data suggests that the word and definition are both contained within a single line of text with the word (or phrase written in all upper-case), it is unclear whether you will have to address the case where the definition can span multiple lines (essentially causing you to potentially read multiple lines and combine them to form the complete definition.
Whenever you need to maintain relationships between multiple variables within a single object, then a struct is a good choice for the base data object. Using an array of struct allows you access to each word and its associated definition once all have been read into memory. Now your example has 3 words and definitions. (each separated by a '\n'). Creating an array of 3 struct to hold the data is trivial, but when reading data, like a dictionary, you rarely know exactly how many words you will have to read.
To handle this situation, a dynamic array of structs is a proper data structure. You essentially allocate space for some reasonable number of words/definitions, and then if you reach that limit, you simply realloc the array containing your data, update your limit to reflect the new size allocated, and continue on.
While you can use strtok to separate the word (or phrase) by looking for the first '.', that is a bit of an overkill. You will need to traverse over each char anyway to check if they are all caps anyway, you may as well just iterate until you find the '.' and use the number for that character index to store your word and set a pointer to the next char after the '.'. You will begin looking for the start of the definition from there (you basically want to skip any character that is not an [a-zA-Z]). Once you locate the beginning of the definition, you can simply get the length of the rest of the line, and copy that as the definition (or the first part of it if the definition is contained in multiple-separate lines).
After the file is read and the pointer returned and the pointer for the number of words updated, you can then use the array of structs back in main as you like. Once you are done using the information, you should free all the memory you have allocated.
Since the size of the maximum word or phrase is generally know, the struct used provides static storage for the word. Give the definitions can vary wildly in length and are much longer, the struct simply contains a pointer-to-char*. So you will have to allocate storage for each struct, and then allocates storage for each definition within each struct.
The following code does just that. It will take the filename to read as the first argument (or it will read from stdin by default if no filename is given). The code the output the words and definitions on single lines. The code is heavily commented to help you follow along and explain the logic e.g.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum {MAXW = 64, NDEF = 128};
typedef struct { /* struct holding words/definitions */
char word[MAXW],
*def; /* you must allocate space for def */
} defn;
defn *readdict (FILE *fp, size_t *n);
int main (int argc, char **argv) {
defn *defs = NULL;
size_t n = 0;
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
if (!(defs = readdict (fp, &n))) { /* read words/defs into defs */
fprintf (stderr, "readdict() error: no words read from file.\n");
return 1;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < n; i++) {
printf ("\nword: %s\n\ndefinition: %s\n", defs[i].word, defs[i].def);
free (defs[i].def); /* free allocated definitions */
}
free (defs); /* free array of structs */
return 0;
}
/** read word and associated definition from open file stream 'fp'
* into dynamic array of struct, updating pointer 'n' to contain
* the total number of defn structs filled.
*/
defn *readdict (FILE *fp, size_t *n)
{
defn *defs = NULL; /* pointer to array of structs */
char buf[BUFSIZ] = ""; /* buffer to hold each line read */
size_t max = NDEF, haveword = 0, offset = 0; /* allocated size & flags */
/* allocate, initialize & validate memory to hold 'max' structs */
if (!(defs = calloc (max, sizeof *defs))) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return NULL;
}
while (fgets (buf, BUFSIZ, fp)) /* read each line of input */
{
if (*buf == '\n') { /* check for blank line */
if (haveword) (*n)++; /* if word/def already read, increment n */
haveword = 0; /* reset haveword flag */
if (*n == max) {
void *tmp = NULL; /* tmp ptr to realloc defs */
if (!(tmp = realloc (defs, sizeof *defs * (max + NDEF)))) {
fprintf (stderr, "error: memory exhaused, realloc defs.\n");
break;
}
defs = tmp; /* assign new block to defs */
memset (defs + max, 0, NDEF * sizeof *defs); /* zero new mem */
max += NDEF; /* update max with current allocation size */
}
continue; /* get next line */
}
if (haveword) { /* word already stored in defs[n].word */
void *tmp = NULL; /* tmp pointer to realloc */
size_t dlen = strlen (buf); /* get line/buf length */
if (buf[dlen - 1] == '\n') /* trim '\n' from end */
buf[--dlen] = 0; /* realloc & validate */
if (!(tmp = realloc (defs[*n].def, offset + dlen + 2))) {
fprintf (stderr,
"error: memory exhaused, realloc defs[%zu].def.\n", *n);
break;
}
defs[*n].def = tmp; /* assign new block, fill with definition */
sprintf (defs[*n].def + offset, offset ? " %s" : "%s", buf);
offset += dlen + 1; /* update offset for rest (if required) */
}
else { /* no current word being defined */
char *p = NULL;
size_t i;
for (i = 0; buf[i] && i < MAXW; i++) { /* check first MAXW chars */
if (buf[i] == '.') { /* if a '.' is found, end of word */
size_t dlen = 0;
if (i + 1 == MAXW) { /* check one char available for '\0' */
fprintf (stderr,
"error: 'word' exceeds MAXW, skipping.\n");
goto next;
}
strncpy (defs[*n].word, buf, i); /* copy i chars to .word */
haveword = 1; /* set haveword flag */
p = buf + i + 1; /* set p to next char in buf after '.' */
while (*p && (*p == ' ' || *p < 'A' || /* find def start */
('Z' < *p && *p < 'a') || 'z' < *p))
p++; /* increment p and check again */
if ((dlen = strlen (p))) { /* get definition length */
if (p[dlen - 1] == '\n') /* trim trailing '\n' */
p[--dlen] = 0;
if (!(defs[*n].def = malloc (dlen + 1))) { /* allocate */
fprintf (stderr,
"error: virtual memory exhausted.\n");
goto done; /* bail if allocation failed */
}
strcpy (defs[*n].def, p); /* copy definition to .def */
offset = dlen; /* set offset in .def buf to be */
} /* used if def continues on a */
break; /* new or separae line */
} /* check word is all upper-case or a ' ' */
else if (buf[i] != ' ' && (buf[i] < 'A' || 'Z' < buf[i]))
break;
}
}
next:;
}
done:;
if (haveword) (*n)++; /* account for last word/definition */
return defs; /* return pointer to array of struct */
}
Example Use/Output
$ ./bin/dict_read <dat/dict.txt
word: ACHROMATIC
definition: An optical term applied to those telescopes in which
aberration of the rays of light, and the colours dependent thereon,
are partially corrected. (See APLANATIC.)
word: ACHRONICAL
definition: An ancient term, signifying the rising of the heavenly
bodies at sunset, or setting at sunrise.
word: ACROSS THE TIDE
definition: A ship riding across tide, with the wind in the direction
of the tide, would tend to leeward of her anchor; but with a weather tide,
or that running against the wind, if the tide be strong, would tend to
windward. A ship under sail should prefer the tack that stems the tide,
with the wind across the stream, when the anchor is let go.
(line breaks were manually inserted to keep the results tidy here).
Memory Use/Error Check
You should also run any code that dynamically allocates memory though a memory use and error checking program like valgrind on linux. Just run the code though it and confirm you free all memory you allocate and that there are no memory errors, e.g.
$ valgrind ./bin/dict_read <dat/dict.txt
==31380== Memcheck, a memory error detector
==31380== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==31380== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==31380== Command: ./bin/dict_read
==31380==
word: ACHROMATIC
<snip output>
==31380==
==31380== HEAP SUMMARY:
==31380== in use at exit: 0 bytes in 0 blocks
==31380== total heap usage: 4 allocs, 4 frees, 9,811 bytes allocated
==31380==
==31380== All heap blocks were freed -- no leaks are possible
==31380==
==31380== For counts of detected and suppressed errors, rerun with: -v
==31380== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Look things over and let me know if you have further questions.
Related
I want to read the entire file(line by line)into a char pointer"name" in the struct array.(Wanna keep the names (can be of arbitrary length) in a dynamically allocated string Then I will divide the readed string(name) into chunks(age name score) in struct.I get seg fault.(file format is:
age name score
25,Rameiro Rodriguez,3
30,Anatoliy Stephanos,0
19,Vahan: Bohuslav,4.2
struct try{
double age;
char *name;
double score;
};
void allocate_struct_array(struct try **parr,int total_line);
int main(){
int count=0,i=0;
char ch;
fileptr = fopen("book.txt", "r");
//total line in the file is calculated
struct try *parr;
allocate_struct_array(&parr,count_lines);
//i got segmentation fault at below.(parsing code is not writed yet just trying to read the file)
while((ch=fgetc(fileptr))!=EOF) {
count++;
if(ch=='\n'){
parr->name=malloc(sizeof(char*)*count+1);
parr[i].name[count+1]='\0';
parr+=1;
count=0;
}
}
fclose(fileptr);
}
void allocate_struct_array(struct try **parr,int total_line){
*parr = malloc(total_line * sizeof(struct try));
}
Continuing from my comment, in allocate_struct_array(struct try **parr,int total_line), you allocate a block of struct try not a block of pointers (e.g. struct try*). Your allocation parr->name=malloc(sizeof(char*)*count+1); attempts to allocate count + 1 pointers. Moreover, on each iteration, you overwrite the address held by parr->name creating a memory leak because the pointer to the prior allocation is lost and cannot be freed.
In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
A better approach to your problem is to read each line into a simply character array (of sufficient size to hold each line). You can then separate age, name and score and determine the number of characters in name so you can properly allocate for parr[i].name and then you can copy the name after you have allocated. If you are careful about it, you can simply locate both ',' in the buffer, allocate for parr[i].name and then use sscanf() with a proper format-string to separate, convert and copy all values to your struct parr[i] in a single call.
Since you have given no way to determine how //total line in the file is calculated, we will just presume a number large enough to accommodate your example file for purposes of discussion. Finding that number is left to you.
To read each line into an array, simply declare a buffer (character array) large enough to hold each line (take your longest expected line and multiply by 2 or 4, or if on a typical PC, just use a buffer of 1024 or 2048 bytes that will accommodate all but the obscure file with lines longer than that. (Rule: Don't Skimp On Buffer Size!!) You can do that with, e.g.
#define COUNTLINES 10 /* if you need a constant, #define one (or more) */
#define MAXC 1024
#define NUMSZ 64
...
int main (int argc, char **argv) {
char buf[MAXC]; /* temporary array to hold each line */
...
When reading until '\n' or EOF in a loop, it is easier to loop continually and check for EOF within the loop. That way the final line is handled as a normal part of your read loop and you don't need a special final code block to handle the last line, e.g.
while (nparr < count_lines) { /* protect your allocation bounds */
int ch = fgetc (fileptr); /* ch must be type int */
if (ch != '\n' && ch != EOF) { /* if not \n and not EOF */
...
}
else if (count) { /* only process buf if chars present */
...
}
if (ch == EOF) { /* if EOF, now break */
break;
}
}
(note: for your example we have continued to read with the fgetc() you used, but in normal practice you would simply use fgets() to fill the character array with the line)
To find the first and last ',' in the array, you can simply #include <string.h> and use strchar() to find the first and strrchr() to find the last. Using a pointer and end-pointer set to the first and last ',' the number of characters in name becomes ep - p - 1;. You can find the ','s and find the length of name with:
char *p = buf, *ep; /* pointer & end-pointer */
...
/* locate 1st ',' with p and last ',' with ep */
if ((p = strchr (buf, ',')) && (ep = strrchr (buf, ',')) &&
p != ep) { /* confirm pointers don't point to same ',' */
size_t len = ep - p - 1; /* get length of name */
Once you have found the first ',' and second ',' and determined the number of characters in name, you allocate characters, not pointers, e.g. with len characters in name and nparr as the struct index (instead of your i) you would do:
parr[nparr].name = malloc (len + 1); /* allocate */
if (!parr[nparr].name) { /* validate */
perror ("malloc-parr[nparr].name");
break;
}
(note: you break instead of exit on allocation error as all prior structs allocated for and filled will still contain valid data that you can use)
Now you can craft a sscanf() format string and separate age, name and score in a single call, e.g.
/* separate buf & convert into age, name, score -- validate */
if (sscanf (buf, "%d,%[^,],%lf", &parr[nparr].age,
parr[nparr].name, &parr[nparr].score) != 3) {
fputs ("error: invalid line format.\n", stderr);
...
}
Putting it altogether into a short program to read and separate your exmaple file, you could do:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define COUNTLINES 10 /* if you need a constant, #define one (or more) */
#define MAXC 1024
#define NUMSZ 64
typedef struct { /* typedef for convenient use as type */
int age; /* age is generally an integer, not double */
char *name;
double score;
} try;
/* always provde a meaningful return when function can
* succeed or fail. Return result of malloc.
*/
try *allocate_struct_array (try **parr, int total_line)
{
return *parr = malloc (total_line * sizeof **parr);
}
int main (int argc, char **argv) {
char buf[MAXC]; /* temporary array to hold each line */
int count = 0,
nparr = 0,
count_lines = COUNTLINES;
try *parr = NULL;
/* use filename provided as 1st argument (book.txt by default) */
FILE *fileptr = fopen (argc > 1 ? argv[1] : "book.txt", "r");
if (!fileptr) { /* always validate file open for reading */
perror ("fopen-fileptr");
return 1;
}
if (!fgets (buf, MAXC, fileptr)) { /* read/discard header line */
fputs ("file-empty\n", stderr);
return 1;
}
/* validate every allocation */
if (allocate_struct_array (&parr, count_lines) == NULL) {
perror ("malloc-parr");
return 1;
}
while (nparr < count_lines) { /* protect your allocation bounds */
int ch = fgetc (fileptr); /* ch must be type int */
if (ch != '\n' && ch != EOF) { /* if not \n and not EOF */
buf[count++] = ch; /* add char to buf */
if (count + 1 == MAXC) { /* validate buf not full */
fputs ("error: line too long.\n", stderr);
count = 0;
continue;
}
}
else if (count) { /* only process buf if chars present */
char *p = buf, *ep; /* pointer & end-pointer */
buf[count] = 0; /* nul-terminate buf */
/* locate 1st ',' with p and last ',' with ep */
if ((p = strchr (buf, ',')) && (ep = strrchr (buf, ',')) &&
p != ep) { /* confirm pointers don't point to same ',' */
size_t len = ep - p - 1; /* get length of name */
parr[nparr].name = malloc (len + 1); /* allocate */
if (!parr[nparr].name) { /* validate */
perror ("malloc-parr[nparr].name");
break;
}
/* separate buf & convert into age, name, score -- validate */
if (sscanf (buf, "%d,%[^,],%lf", &parr[nparr].age,
parr[nparr].name, &parr[nparr].score) != 3) {
fputs ("error: invalid line format.\n", stderr);
if (ch == EOF) /* if at EOF on failure */
break; /* break read loop */
else {
count = 0; /* otherwise reset count */
continue; /* start read of next line */
}
}
}
nparr += 1; /* increment array index */
count=0; /* reset count zero */
}
if (ch == EOF) { /* if EOF, now break */
break;
}
}
fclose(fileptr); /* close file */
for (int i = 0; i < nparr; i++) {
printf ("%3d %-20s %5.1lf\n",
parr[i].age, parr[i].name, parr[i].score);
free (parr[i].name); /* free strings when done */
}
free (parr); /* free struxts */
}
(note: Never Hardcode Filenames or use Magic-Numbers in your code. If you need a constant, #define ... one. Pass the filename to read as the first argument to your program or take the filename as input. You shouldn't have to recompile your code just to read from a different filename)
Example Use/Output
With your example data in dat/parr_name.txt, you would have:
$ ./bin/parr_name dat/parr_name.txt
25 Rameiro Rodriguez 3.0
30 Anatoliy Stephanos 0.0
19 Vahan: Bohuslav 4.2
Memory Use/Error Check
It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ valgrind ./bin/parr_name dat/parr_name.txt
==17385== Memcheck, a memory error detector
==17385== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==17385== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==17385== Command: ./bin/parr_name dat/parr_name.txt
==17385==
25 Rameiro Rodriguez 3.0
30 Anatoliy Stephanos 0.0
19 Vahan: Bohuslav 4.2
==17385==
==17385== HEAP SUMMARY:
==17385== in use at exit: 0 bytes in 0 blocks
==17385== total heap usage: 7 allocs, 7 frees, 5,965 bytes allocated
==17385==
==17385== All heap blocks were freed -- no leaks are possible
==17385==
==17385== For counts of detected and suppressed errors, rerun with: -v
==17385== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.
Using fgets() To Read Each Line And A Temp Array For name
To not leave you with the wrong impression, this problem can be simplified substantially by reading each line into a character array using fgets() and separating the needed values with sscanf(), saving name into a temporary array of sufficient size. Now all that is needed is to allocate for parr[nparr].name and then copy the temporary name to parr[nparr].name.
By doing it this way you substantially reduce the complexity of reading character-by-character and by using a temporary array for name, you eliminate having to locate the ',' in order to obtain the length of the name.
The only changes needed are to add a new constant for the temporary name array and then you can replace the entire read-loop with:
#define NAMSZ 256
...
/* protect memory bounds, read each line into buf */
while (nparr < count_lines && fgets (buf, MAXC, fileptr)) {
char name[NAMSZ]; /* temporary array for name */
size_t len; /* length of name */
/* separate buf into age, temp name, score & validate */
if (sscanf (buf, "%d,%[^,],%lf", &parr[nparr].age, name,
&parr[nparr].score) != 3) {
fputs ("error: invalid line format.\n", stderr);
continue;
}
len = strlen (name); /* get length of name */
parr[nparr].name = malloc (len + 1); /* allocate for name */
if (!parr[nparr].name) { /* validate allocation */
perror ("malloc-parr[nparr].name");
break;
}
memcpy (parr[nparr].name, name, len + 1);
nparr += 1;
}
fclose(fileptr); /* close file */
...
(same output and same memory check)
Also note you can allocate and copy as a single operation if your compiler provides strdup(). That would reduce the allocation and copy of name to a single call, e.g.
parr[nparr].name = strdup (name);
Since strdup() allocates memory (and can fail), you must validate the allocation just as you would if you were using malloc() amd memcpy(). But, understand, strdup() is not standard C. It is a POSIX function that isn't part of the standard library.
The other improvement you can make is adding logic to call realloc() when your block of struct (parr) is full. That way you can start with some reasonably anticipated number of struct and then reallocate more whenever you run out. This will eliminate the artificial limit on the number of lines you can store -- and remove the need to know count_lines. (there are numerous examples on this site of how to use realloc(), the implementation is left to you.
Look things over and let me know if you have further questions.
I am trying to parse a csv into a dynamically allocated array of structures, however my attempt crashes with a segmentation fault.
Here is the structure of my data:
SO02773202,5087001,0
SO02773203,5087001,0
SO02773204,5087001,0
SO02773205,5087001,0
SO02773206,5087001,14
This is the struct I am parsing the data into:
typedef struct saleslines{
char* salesid;
char* smmcampaignid;
int numberofbottles;
} saleslines_t;
Here is my attempt at parsing the file:
int read_saleslines(saleslines_t* saleslines, int number_of_lines){
char c;
FILE* fp;
fp = fopen(FILENAME, "r"); /* Open the saleslines file */
if(fp == NULL){ /* Crash if file not found */
printf("Error - file not found\n");
return 0;
}
c = getc(fp);
while (c != EOF){
if (c == '\n'){
number_of_lines += 1;
}
c = getc(fp);
}
printf("Number of lines is %d\n", number_of_lines);
saleslines = (saleslines_t*) malloc((number_of_lines * 2) * sizeof(saleslines_t));
/* allocation of the buffer for every line in the File */
char *buf = (char*) malloc(1000);
char *tmp;
if ( ( fp = fopen(FILENAME, "r" ) ) == NULL )
{
printf( "File could not be opened.\n" );
}
int i = 0;
while (fgets(buf, 255, fp) != NULL){
if ((strlen(buf)>0) && (buf[strlen (buf) - 1] == '\n'))
buf[strlen (buf) - 1] = '\0';
tmp = strtok(buf, ",");
saleslines[i].salesid = strdup(tmp);
tmp = strtok(NULL, ",");
saleslines[i].smmcampaignid = strdup(tmp);
tmp = strtok(NULL, ",");
saleslines[i].numberofbottles = atoi(tmp);
printf("Salesid: %s\nCampaign: %s\nBottles: %i\n\n", saleslines[i].salesid , saleslines[i].smmcampaignid, saleslines[i].numberofbottles);
i++;
}
free(buf);
fclose(fp);
printf("Number of lines is %i\n", number_of_lines);
return number_of_lines;
}
For some reason it parses the file and prints the resulting array of structs, however when I call this function immediately after, it crashes with a segfault:
void print_saleslines_struct(saleslines_t* saleslines, int number_of_lines{
int i;
printf("Number of lines is %i", number_of_lines);
for(i = 0; i < number_of_lines; i++){
printf("Salesid:\t %s\n", saleslines[i].salesid);
printf("Campaign:\t %s\n", saleslines[i].smmcampaignid);
printf("# of Bottles:\t %d\n", saleslines[i].numberofbottles);
}
}
I can't seem to find where this memory bug is.
Here is the initialization and main:
saleslines_t* saleslines;
saleslines_summary_t* saleslines_summary;
saleslines_grouped_t* saleslines_grouped;
int number_of_lines = 0;
int* number_of_linesp = &number_of_lines;
/* Main */
int main(){
int chosen_option;
while(1){
printf("What would you like to do?\n");
printf("1. Read saleslines.txt\n");
printf("2. Print saleslines\n");
printf("3. Summarise saleslines\n");
printf("4. Exit the program\n");
scanf("%d", &chosen_option);
switch(chosen_option){
/* case 1 : number_of_lines = read_saleslines_file(saleslines, number_of_lines); break; */
case 1 : number_of_lines = read_saleslines(saleslines, number_of_lines); break;
case 2 : printf("Number of lines is %i", number_of_lines); print_saleslines_struct(saleslines, number_of_lines); break;
case 3 : summarise_saleslines(saleslines, number_of_linesp, saleslines_summary, saleslines_grouped); break;
case 4 : free(saleslines); free(saleslines_summary); free(saleslines_grouped); return 0;
}
}
return 0;
}
Update
The issue seems to be with my initialization of the array of structures.
When I initialize it like this: saleslines_t* saleslines;
and then malloc like this: saleslines = malloc(number_of_lines + 1 * sizeof(saleslines_t);
I get a segfault.
But if I initialize like this: saleslines[600]; (allocating more than the number of lines in the file), everything works.
How can I get around this? I would like to be able to dynamically allocate the number of entries within the struct array.
Edit 2
Here are the changes as suggested:
int read_saleslines(saleslines_t** saleslines, int number_of_lines);
saleslines_t* saleslines;
int number_of_lines = 0;
int main(){
while(1){
printf("What would you like to do?\n");
printf("1. Read saleslines.txt\n");
printf("2. Print saleslines\n");
printf("3. Summarise saleslines\n");
printf("4. Exit the program\n");
printf("Number of saleslines = %i\n", number_of_lines);
scanf("%d", &chosen_option);
switch(chosen_option){
/* case 1 : number_of_lines = read_saleslines_file(saleslines, number_of_lines); break; */
case 1 : number_of_lines = read_saleslines(&saleslines, number_of_lines); break;
case 2 : printf("Number of lines is %i", number_of_lines); print_saleslines_struct(saleslines, number_of_lines); break;
case 3 : summarise_saleslines(saleslines, number_of_linesp, saleslines_summary, saleslines_grouped); break;
case 4 : free(saleslines); free(saleslines_summary); free(saleslines_grouped); return 0;
}
}
return 0;
}
int read_saleslines(saleslines_t** saleslines, int number_of_lines)
{
char c;
FILE* fp;
fp = fopen(FILENAME, "r"); /* Open the saleslines file */
if(fp == NULL){ /* Crash if file not found */
printf("Error - file not found\n");
return 0;
}
c = getc(fp);
while (c != EOF){
if (c == '\n'){
number_of_lines += 1;
}
c = getc(fp);
}
fclose(fp);
printf("Number of lines is %d\n", number_of_lines);
*saleslines = (saleslines_t*) malloc((number_of_lines + 1) * sizeof(saleslines_t));
/* allocation of the buffer for every line in the File */
char *buf = malloc(25);
char *tmp;
if ( ( fp = fopen(FILENAME, "r" ) ) == NULL )
{
printf( "File could not be opened.\n" );
}
int i = 0;
while (fgets(buf, 25, fp) != NULL){
if ((strlen(buf)>0) && (buf[strlen (buf) - 1] == '\n'))
buf[strlen (buf) - 1] = '\0';
tmp = strtok(buf, ",");
(*saleslines)[i].salesid = strdup(tmp);
tmp = strtok(NULL, ",");
(*saleslines)[i].smmcampaignid = strdup(tmp);
tmp = strtok(NULL, ",");
(*saleslines)[i].numberofbottles = atoi(tmp);
printf("Salesid: %s\nCampaign: %s\nBottles: %i\n\n", saleslines[i]->salesid , saleslines[i]->smmcampaignid, saleslines[i]->numberofbottles);
i++;
}
free(buf);
fclose(fp);
printf("Number of lines is %i\n", number_of_lines);
return number_of_lines;
}
The program now segfaults after reading the first element in the struct array.
You have a problem with the arguments of read_saleslines(). The first argument should be a pointer to an array of your structs, meaning a double pointer.
In
int read_saleslines(saleslines_t* saleslines, int number_of_lines){
you want to modify where saleslines is pointing. saleslines is a local variable of the function, and the scope is that function. Once you exit read_saleslines(), the variable is "destroyed", meaning that the value it holds it is not accessible anymore. Adding another level of indirection, a pointer, you can modify the variable that's defined outside the function, being that (ugly) global or other. So, change that argument so that the function prototype matches
int read_saleslines(saleslines_t** saleslines, int *);
and change the places where you access it inside the function (adding an * to access it, for example:
saleslines = (saleslines_t*) malloc((number_of_lines * ...
to
*saleslines = (saleslines_t*) malloc((number_of_lines * ...
and
saleslines[i].salesid = strdup(tmp);
to
(*saleslines)[i].salesid = strdup(tmp);
Then add an & where you use the variable outside the function:
number_of_lines = read_saleslines(saleslines, number_of_lines);
changes to
some_var = read_saleslines(&saleslines, &number_of_lines);
That will make you code work.
You have a large number of errors in your code, and with your approach in general. There is no need to make two-passes over the file to determine the number of lines before allocating and then re-reading the file in an attempt to parse the data. Further, there is no need to tokenize each line to separate the comma-separated-values, sscanf() to parse the two strings and one int is sufficient here after reading each line with fgets.
While you are free to pass any mix of parameters you like and return whatever you like, since you are allocating for an array of struct and reading values into the array, it makes sense to return a pointer to the allocated array from your function (or NULL on failure) and simply update a parameter passed as a pointer to make the total number of lines read available back in the caller.
Further, generally you want to open and validate the file in the caller and pass a FILE* parameter passing the open file stream to your function. With that in mind, you could refactor your function as:
/* read saleslines into array of saleslines_t, allocating for
* salesid, and smmcampaignid within each struct. Return pointer
* to allocated array on success with lines updated to hold the
* number of elements, or NULL otherwise.
*/
saleslines_t *read_saleslines (FILE *fp, size_t *lines)
{
Within your function, you simply need a buffer to hold each line read, a counter to track the number of elements allocated in your array, and a pointer to your array to return. For example, you could do something like the following to handle all three:
char buf[MAXC]; /* buffer to hold line */
size_t maxlines = MINL; /* maxlines allocated */
saleslines_t *sales = NULL; /* pointer to array of struct */
(note: since you are tracking the number of lines read through the pointer lines passed as a parameter, it would make sense to initialize the value at that address to zero)
Now the work of your function begins, you want to read each line into buf and parse the needed information from each line. Since salesid and smmcampaignid are both pointers-to-char in your struct, you will need to allocate a block of memory for each string parsed from the line, copy the string to the new block of memory, and then assign the beginning address for the bock to each of your pointers. To "dynamically" handle allocating elements for your struct, you simply check if the number of lines (*lines) filled equals against the number allocated (maxlines), (or if *lines is zero indicating a need for an initial allocation), and realloc in both cases to either realloc (or newly allocate) storage for your array of struct.
When you realloc you always realloc using a temporary pointer so if realloc fails and returns NULL, you don't overwrite your pointer to the currently allocated block with NULL thereby creating a memory leak.
Putting all that together at the beginning of your function may seem daunting, but it is actually straight forward, e.g.
while (fgets (buf, MAXC, fp)) { /* read each line in file */
char id[MAXC], cid[MAXC]; /* temp arrays to hold strings */
int bottles; /* temp int for numberofbottles */
if (*lines == maxlines || !*lines) { /* check if realloc req'd */
/* always realloc with a temp pointer */
void *tmp = realloc (sales, 2 * maxlines * sizeof *sales);
if (!tmp) { /* if realloc fails, original pointer still valid */
perror ("realloc-sales"); /* throw error */
return sales; /* return current pointer */
} /* (don't exit or return NULL) */
sales = tmp; /* assign reallocated block to sales */
/* (optional) zero newly allocated memory */
memset (sales + *lines, 0, maxlines * sizeof *sales);
maxlines *= 2; /* update maxlines allocated */
}
Now you are ready to parse the wanted information from your line with sscanf, and then following a successful parse of information, you can allocate for each of your salesid and smmcampaignid pointers, copy the parsed information to the new blocks of memory assigning the beginning address to each pointer, respectively, e.g.
/* parse needed data from line (sscanf is fine here) */
if (sscanf (buf, "%1023[^,],%1023[^,],%d", id, cid, &bottles) == 3) {
size_t idlen = strlen (id), /* get lengths of strings */
cidlen = strlen (cid);
sales[*lines].salesid = malloc (idlen + 1); /* allocate string */
if (!sales[*lines].salesid) { /* validate! */
perror ("malloc-sales[*lines].salesid");
break;
}
sales[*lines].smmcampaignid = malloc (cidlen + 1); /* ditto */
if (!sales[*lines].smmcampaignid) {
perror ("malloc-sales[*lines].smmcampaignid");
break;
}
memcpy (sales[*lines].salesid, id, idlen + 1); /* copy strings */
memcpy (sales[*lines].smmcampaignid, cid, cidlen + 1);
sales[(*lines)++].numberofbottles = bottles; /* assign int */
} /* (note lines counter updated in last assignment) */
(note: you can use strdup to both get the length of each string parsed and allocate sufficient memory to hold the string and assign that to your pointer in one-shot, e.g. sales[*lines].salesid = strdup (id);, but... strdup is not required to be included in C99 or later, so it is just as simple to get the length, allocate length + 1 bytes and then memcpy your string manually to ensure portability. Further, since strdup allocates memory, you must validate the pointer returned -- something overlooked by 99% of those using it.)
That's it, when fgets() fails, you have reached EOF, now simply:
return sales; /* return dynamically allocated array of struct */
}
Putting it altogether in a short, working example that takes the filename to read as the first argument to your program (or reads from stdin by default if no argument is given), you could do:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define MINL 2
typedef struct saleslines{
char *salesid;
char *smmcampaignid;
int numberofbottles;
} saleslines_t;
/* read saleslines into array of saleslines_t, allocating for
* salesid, and smmcampaignid within each struct. Return pointer
* to allocated array on success with lines updated to hold the
* number of elements, or NULL otherwise.
*/
saleslines_t *read_saleslines (FILE *fp, size_t *lines)
{
char buf[MAXC]; /* buffer to hold line */
size_t maxlines = MINL; /* maxlines allocated */
saleslines_t *sales = NULL; /* pointer to array of struct */
*lines = 0; /* zero lines */
while (fgets (buf, MAXC, fp)) { /* read each line in file */
char id[MAXC], cid[MAXC]; /* temp arrays to hold strings */
int bottles; /* temp int for numberofbottles */
if (*lines == maxlines || !*lines) { /* check if realloc req'd */
/* always realloc with a temp pointer */
void *tmp = realloc (sales, 2 * maxlines * sizeof *sales);
if (!tmp) { /* if realloc fails, original pointer still valid */
perror ("realloc-sales"); /* throw error */
return sales; /* return current pointer */
} /* (don't exit or return NULL) */
sales = tmp; /* assign reallocated block to sales */
/* (optional) zero newly allocated memory */
memset (sales + *lines, 0, maxlines * sizeof *sales);
maxlines *= 2; /* update maxlines allocated */
}
/* parse needed data from line (sscanf is fine here) */
if (sscanf (buf, "%1023[^,],%1023[^,],%d", id, cid, &bottles) == 3) {
size_t idlen = strlen (id), /* get lengths of strings */
cidlen = strlen (cid);
sales[*lines].salesid = malloc (idlen + 1); /* allocate string */
if (!sales[*lines].salesid) { /* validate! */
perror ("malloc-sales[*lines].salesid");
break;
}
sales[*lines].smmcampaignid = malloc (cidlen + 1); /* ditto */
if (!sales[*lines].smmcampaignid) {
perror ("malloc-sales[*lines].smmcampaignid");
break;
}
memcpy (sales[*lines].salesid, id, idlen + 1); /* copy strings */
memcpy (sales[*lines].smmcampaignid, cid, cidlen + 1);
sales[(*lines)++].numberofbottles = bottles; /* assign int */
} /* (note lines counter updated in last assignment) */
}
return sales; /* return dynamically allocated array of struct */
}
int main (int argc, char **argv) {
saleslines_t *sales = NULL; /* pointer to saleslines_t */
size_t nlines;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
sales = read_saleslines (fp, &nlines); /* read saleslines */
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < nlines; i++) { /* loop over each */
printf ("sales[%2zu]: %s %s %2d\n", i, sales[i].salesid,
sales[i].smmcampaignid, sales[i].numberofbottles);
free (sales[i].salesid); /* free salesid */
free (sales[i].smmcampaignid); /* free smmcampaignid */
}
free (sales); /* free sales */
return 0;
}
Example Use/Output
$ ./bin/saleslines dat/saleslines.txt
sales[ 0]: SO02773202 5087001 0
sales[ 1]: SO02773203 5087001 0
sales[ 2]: SO02773204 5087001 0
sales[ 3]: SO02773205 5087001 0
sales[ 4]: SO02773206 5087001 14
Memory Use/Error Check
In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ valgrind ./bin/saleslines dat/saleslines.txt
==19819== Memcheck, a memory error detector
==19819== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==19819== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==19819== Command: ./bin/saleslines dat/saleslines.txt
==19819==
sales[ 0]: SO02773202 5087001 0
sales[ 1]: SO02773203 5087001 0
sales[ 2]: SO02773204 5087001 0
sales[ 3]: SO02773205 5087001 0
sales[ 4]: SO02773206 5087001 14
==19819==
==19819== HEAP SUMMARY:
==19819== in use at exit: 0 bytes in 0 blocks
==19819== total heap usage: 13 allocs, 13 frees, 935 bytes allocated
==19819==
==19819== All heap blocks were freed -- no leaks are possible
==19819==
==19819== For counts of detected and suppressed errors, rerun with: -v
==19819== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.
There is nothing difficult in dynamically allocating for anything. Just take it in small enough pieces that you dot all "I's" and cross all "T's" for each pointer requiring allocation. Look things over and let me know if you have further questions.
This code reads a text file line by line. But I need to put those lines in an array but I wasn't able to do it. Now I am getting a array of numbers somehow. So how to read the file into a list. I tried using 2 dimensional list but this doesn't work as well.
I am new to C. I am mostly using Python but now I want to check if C is faster or not for a task.
#include <stdio.h>
#include <time.h>
#include <string.h>
void loadlist(char *ptext) {
char filename[] = "Z://list.txt";
char myline[200];
FILE * pfile;
pfile = fopen (filename, "r" );
char larray[100000];
int i = 0;
while (!feof(pfile)) {
fgets(myline,200,pfile);
larray[i]= myline;
//strcpy(larray[i],myline);
i++;
//printf(myline);
}
fclose(pfile);
printf("%s \n %d \n %d \n ","while doneqa",i,strlen(larray));
printf("First larray element is: %d \n",larray[0]);
/* for loop execution */
//for( i = 10; i < 20; i = i + 1 ){
// printf(larray[i]);
//}
}
int main ()
{
time_t stime, etime;
printf("Starting of the program...\n");
time(&stime);
char *ptext = "String";
loadlist(ptext);
time(&etime);
printf("time to load: %f \n", difftime(etime, stime));
return(0);
}
This code reads a text file line by line. But I need to put those lines in an array but I wasn't able to do it. Now I am getting an array of numbers somehow.
There are many ways to do this correctly. To begin with, first sort out what it is you actually need/want to store, then figure out where that information will come from and finally decide how you will provide storage for the information. In your case loadlist is apparently intended load a list of lines (up to 10000) so that they are accessible through your statically declared array of pointers. (you can also allocate the pointers dynamically, but if you know you won't need more than X of them, statically declaring them is fine (up to the point you cause StackOverflow...)
Once you read the line in loadlist, then you need to provide adequate storage to hold the line (plus the nul-terminating character). Otherwise, you are just counting the number of lines. In your case, since you declare an array of pointers, you cannot simply copy the line you read because each of the pointers in your array does not yet point to any allocated block of memory. (you can't assign the address of the buffer you read the line into with fgets (buffer, size, FILE*) because (1) it is local to your loadlist function and it will go away when the function stack frame is destroyed on function return; and (2) obviously it gets overwritten with each call to fgets anyway.
So what to do? That's pretty simple too, just allocate storage for each line as it is read using the strlen of each line as #iharob says (+1 for the nul-byte) and then malloc to allocate a block of memory that size. You can then simply copy the read buffer to the block of memory created and assign the pointer to your list (e.g. larray[x] in your code). Now the gnu extensions provide a strdup function that both allocates and copies, but understand that is not part of the C99 standard so you can run into portability issues. (also note you can use memcpy if overlapping regions of memory are a concern, but we will ignore that for now since you are reading lines from a file)
What are the rules for allocating memory? Well, you allocate with malloc, calloc or realloc and then you VALIDATE that your call to those functions succeeded before proceeding or you have just entered the realm of undefined behavior by writing to areas of memory that are NOT in fact allocated for your use. What does that look like? If you have your array of pointers p and you want to store a string from your read buffer buf of length len at index idx, you could simply do:
if ((p[idx] = malloc (len + 1))) /* allocate storage */
strcpy (p[idx], buf); /* copy buf to storage */
else
return NULL; /* handle error condition */
Now you are free to allocate before you test as follows, but it is convenient to make the assignment as part of the test. The long form would be:
p[idx] = malloc (len + 1); /* allocate storage */
if (p[idx] == NULL) /* validate/handle error condition */
return NULL;
strcpy (p[idx], buf); /* copy buf to storage */
How you want to do it is up to you.
Now you also need to protect against reading beyond the end of your pointer array. (you only have a fixed number since you declared the array statically). You can make that check part of your read loop very easily. If you have declared a constant for the number of pointers you have (e.g. PTRMAX), you can do:
int idx = 0; /* index */
while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
...
idx++;
}
By checking the index against the number of pointers available, you insure you cannot attempt to assign address to more pointers than you have.
There is also the unaddressed issue of handling the '\n' that will be contained at the end of your read buffer. Recall, fgets read up to and including the '\n'. You do not want newline characters dangling off the ends of the strings you store, so you simply overwrite the '\n' with a nul-terminating character (e.g. simply decimal 0 or the equivalent nul-character '\0' -- your choice). You can make that a simple test after your strlen call, e.g.
while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
size_t len = strlen (buf); /* get length */
if (buf[len-1] == '\n') /* check for trailing '\n' */
buf[--len] = 0; /* overwrite '\n' with nul-byte */
/* else { handle read of line longer than 200 chars }
*/
...
(note: that also brings up the issue of reading a line longer than the 200 characters you allocate for your read buffer. You check for whether a complete line has been read by checking whether fgets included the '\n' at the end, if it didn't, you know your next call to fgets will be reading again from the same line, unless EOF is encountered. In that case you would simply need to realloc your storage and append any additional characters to that same line -- that is left for future discussion)
If you put all the pieces together and choose a return type for loadlist that can indicate success/failure, you could do something similar to the following:
/** read up to PTRMAX lines from 'fp', allocate/save in 'p'.
* storage is allocated for each line read and pointer
* to allocated block is stored at 'p[x]'. (you should
* add handling of lines greater than LNMAX chars)
*/
char **loadlist (char **p, FILE *fp)
{
int idx = 0; /* index */
char buf[LNMAX] = ""; /* read buf */
while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
size_t len = strlen (buf); /* get length */
if (buf[len-1] == '\n') /* check for trailing '\n' */
buf[--len] = 0; /* overwrite '\n' with nul-byte */
/* else { handle read of line longer than 200 chars }
*/
if ((p[idx] = malloc (len + 1))) /* allocate storage */
strcpy (p[idx], buf); /* copy buf to storage */
else
return NULL; /* indicate error condition in return */
idx++;
}
return p; /* return pointer to list */
}
note: you could just as easily change the return type to int and return the number of lines read, or pass a pointer to int (or better yet size_t) as a parameter to make the number of lines stored available back in the calling function.
However, in this case, we have used the initialization of all pointers in your array of pointers to NULL, so back in the calling function we need only iterate over the pointer array until the first NULL is encountered in order to traverse our list of lines. Putting together a short example program that read/stores all lines (up to PTRMAX lines) from the filename given as the first argument to the program (or from stdin if no filename is given), you could do something similar to:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
enum { LNMAX = 200, PTRMAX = 10000 };
char **loadlist (char **p, FILE *fp);
int main (int argc, char **argv) {
time_t stime, etime;
char *list[PTRMAX] = { NULL }; /* array of ptrs initialized NULL */
size_t n = 0;
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
printf ("Starting of the program...\n");
time (&stime);
if (loadlist (list, fp)) { /* read lines from fp into list */
time (&etime);
printf("time to load: %f\n\n", difftime (etime, stime));
}
else {
fprintf (stderr, "error: loadlist failed.\n");
return 1;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
while (list[n]) { /* output stored lines and free allocated mem */
printf ("line[%5zu]: %s\n", n, list[n]);
free (list[n++]);
}
return(0);
}
/** read up to PTRMAX lines from 'fp', allocate/save in 'p'.
* storage is allocated for each line read and pointer
* to allocated block is stored at 'p[x]'. (you should
* add handling of lines greater than LNMAX chars)
*/
char **loadlist (char **p, FILE *fp)
{
int idx = 0; /* index */
char buf[LNMAX] = ""; /* read buf */
while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
size_t len = strlen (buf); /* get length */
if (buf[len-1] == '\n') /* check for trailing '\n' */
buf[--len] = 0; /* overwrite '\n' with nul-byte */
/* else { handle read of line longer than 200 chars }
*/
if ((p[idx] = malloc (len + 1))) /* allocate storage */
strcpy (p[idx], buf); /* copy buf to storage */
else
return NULL; /* indicate error condition in return */
idx++;
}
return p; /* return pointer to list */
}
Finally, in any code your write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
Use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory, attempted to read or base a jump on an uninitialized value and finally to confirm that you have freed all the memory you have allocated.
For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
Look things over, let me know if you have any further questions.
It's natural that you see numbers because you are printing a single character using the "%d" specifier. In fact, strings in c are pretty much that, arrays of numbers, those numbers are the ascii values of the corresponding characters. If you instead use "%c" you will see the character that represents each of those numbers.
Your code also, calls strlen() on something that is intended as a array of strings, strlen() is used to compute the length of a single string, a string being an array of char items with a non-zero value, ended with a 0. Thus, strlen() is surely causing undefined behavior.
Also, if you want to store each string, you need to copy the data like you tried in the commented line with strcpy() because the array you are using for reading lines is overwritten over and over in each iteration.
Your compiler must be throwing all kinds of warnings, if it's not then it's your fault, you should let the compiler know that you want it to do some diagnostics to help you find common problems like assigning a pointer to a char.
You should fix multiple problems in your code, here is a code that fixes most of them
void
loadlist(const char *const filename) {
char line[100];
FILE *file;
// We can only read 100 lines, of
// max 99 characters each
char array[100][100];
int size;
size = 0;
file = fopen (filename, "r" );
if (file == NULL)
return;
while ((fgets(line, sizeof(line), file) != NULL) && (size < 100)) {
strcpy(array[size++], line);
}
fclose(file);
for (int i = 0 ; i < size ; ++i) {
printf("array[%d] = %s", i + 1, array[i]);
}
}
int
main(void)
{
time_t stime, etime;
printf("Starting of the program...\n");
time(&stime);
loadlist("Z:\\list.txt");
time(&etime);
printf("Time to load: %f\n", difftime(etime, stime));
return 0;
}
Just to prove how complicated it can be in c, check this out
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
struct string_list {
char **items;
size_t size;
size_t count;
};
void
string_list_print(struct string_list *list)
{
// Simply iterate through the list and
// print every item
for (size_t i = 0 ; i < list->count ; ++i) {
fprintf(stdout, "item[%zu] = %s\n", i + 1, list->items[i]);
}
}
struct string_list *
string_list_create(size_t size)
{
struct string_list *list;
// Allocate space for the list object
list = malloc(sizeof *list);
if (list == NULL) // ALWAYS check this
return NULL;
// Allocate space for the items
// (starting with `size' items)
list->items = malloc(size * sizeof *list->items);
if (list->items != NULL) {
// Update the list size because the allocation
// succeeded
list->size = size;
} else {
// Be optimistic, maybe realloc will work next time
list->size = 0;
}
// Initialize the count to 0, because
// the list is initially empty
list->count = 0;
return list;
}
int
string_list_append(struct string_list *list, const char *const string)
{
// Check if there is room for the new item
if (list->count + 1 >= list->size) {
char **items;
// Resize the array, there is no more room
items = realloc(list->items, 2 * list->size * sizeof *list->items);
if (items == NULL)
return -1;
// Now update the list
list->items = items;
list->size += list->size;
}
// Copy the string into the array we simultaneously
// increase the `count' and copy the string
list->items[list->count++] = strdup(string);
return 0;
}
void
string_list_destroy(struct string_list *const list)
{
// `free()' does work with a `NULL' argument
// so perhaps as a principle we should too
if (list == NULL)
return;
// If the `list->items' was initialized, attempt
// to free every `strdup()'ed string
if (list->items != NULL) {
for (size_t i = 0 ; i < list->count ; ++i) {
free(list->items[i]);
}
free(list->items);
}
free(list);
}
struct string_list *
loadlist(const char *const filename) {
char line[100]; // A buffer for reading lines from the file
FILE *file;
struct string_list *list;
// Create a new list, initially it has
// room for 100 strings, but it grows
// automatically if needed
list = string_list_create(100);
if (list == NULL)
return NULL;
// Attempt to open the file
file = fopen (filename, "r");
// On failure, we now have the responsibility
// to cleanup the allocated space for the string
// list
if (file == NULL) {
string_list_destroy(list);
return NULL;
}
// Read lines from the file until there are no more
while (fgets(line, sizeof(line), file) != NULL) {
char *newline;
// Remove the trainling '\n'
newline = strchr(line, '\n');
if (newline != NULL)
*newline = '\0';
// Append the string to the list
string_list_append(list, line);
}
fclose(file);
return list;
}
int
main(void)
{
time_t stime, etime;
struct string_list *list;
printf("Starting of the program...\n");
time(&stime);
list = loadlist("Z:\\list.txt");
if (list != NULL) {
string_list_print(list);
string_list_destroy(list);
}
time(&etime);
printf("Time to load: %f\n", difftime(etime, stime));
return 0;
}
Now, this will work almost as the python code you say you wrote but it will certainly be faster, there is absolutely no doubt.
It is possible that an experimented python programmer can write a python program that runs faster than that of a non-experimented c programmer, learning c however is really good because you then understand how things work really, and you can then infer how a python feature is probably implemented, so understanding this can be very useful actually.
Although it's certainly way more complicated than doing the same in python, note that I wrote this in nearly 10min. So if you really know what you're doing and you really need it to be fast c is certainly an option, but you need to learn many concepts that are not clear to higher level languages programmers.
This is for a college introductory CS course I am taking. I have been stuck on this problem for a few days, and our department resources have been swamped with other students on this assignment. Searching has not been much help, but I'm not sure I'm wording it correctly.
I am trying to write a program which reads words from a text file, compares them against another text file containing a list of correctly spelled words, and prints the incorrect words from the first text file.
I have written one while(fgets) loop to read each line of the input text file, a while loop nested inside it to tokenize each line into individual words, and finally another while(fgets) loop nested inside that to compare each token against each line in the dictionary file. I have seen some questions in which the inside loop has to be "reset", but I am using the strtok function to do that.
Here is a link to a gist of the code with samples of the input files.
Here is the output from this input:
Misspelled words in words.txt :
hello, A do not match
hello, AA do not match
hello, AAA do not match
hello, thirtytwomo do not match
hello, this do not match
hello, thisaway do not match
hello, contains do not match
hello, few do not match
hello, words do not match
hello, hello match
this
contanes
a
few
words
they
are
seperated
by
multaple
And this is the relevant loop in question:
while (fgets(tempBuffer, sizen, instream) != NULL) {
tempBuffer[strlen(tempBuffer) - 1] = '\0';
//remove the endline character from the line
char *tempToken = strtok(tempBuffer, " ");
//tokenize the line on space:
while (tempToken != NULL)
//will be null at the end of each line
{
char *tempCheck = malloc(sizeof(char) * (strlen(tempToken) + 1));
//build dynamic array to hold string to check
strcpy(tempCheck, tempToken);
while (fgets(tempDictBuffer, sizen, dictInstream) != NULL) {
//compares against each line in dictionary
tempDictBuffer[strlen(tempDictBuffer) - 1] = '\0';
//remove the endline character from the line
char *tempDict = malloc(
sizeof(char) * (strlen(tempDictBuffer) + 1));
//build dynamic array to hold string from dictionary
strcpy(tempDict, tempDictBuffer);
if (strcmp(tempCheck, tempDict) == 0) {
printf("%s, %s match\n", tempCheck, tempDict);
//if the string matches a dictionary line, this prints
result = 1;
//sets flag
} else {
printf("%s, %s do not match\n", tempCheck, tempDict);
//if the string does not match, this prints
}
free(tempDict);
tempDict = NULL;
}
if (result != 1) {
printf("%s\n", tempCheck);
//checks flag
}
result = 0;
//resets flag
free(tempCheck);
tempCheck = NULL;
tempToken = strtok(NULL, " ");
//gets next token in line and reruns second while loop
}
Thanks for any help you can provide!
This is somewhat coincidental, but I happened to have a few functions that essentially do what it is you are trying to do. It may not be a perfect fit, but the following with read 2 files, load the lines of each into an array of pointers to char, then split each line into tokens and compare each of the respective tokens to determine if spelling differs and output the words on each line that are not spelled the same.
It may provide you with a few additional ideas about how to approach your problem. Note, this is by way of example, and not represented to be fully tested for all corner-cases, etc. Since you were allocating storage dynamically, it helps to continue that approach in tokenizing each line. A function that will fully tokenize each line and return the words in an array of pointers to char, cuts down substantially on the number and type of nested loops required. For what it's worth, take a look. Also note that the prn_chararray function is not used in the code below, but is left as a convenience:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NMAX 256
#define BUFL 64
#define MAXS 32
char **readtxtfile (char *fn, size_t *idx);
char **splitstr (char *s, size_t *n);
char **realloc_char (char **p, size_t *n);
void prn_chararray (char **ca);
void free_chararray (char **ca);
int main (int argc, char **argv) {
if (argc < 3 ) {
fprintf (stderr, "error: insufficient input, usage: %s <filename1> <filename2>\n", argv[0]);
return 1;
}
size_t file1_size = 0; /* placeholders to be filled by readtxtfile */
size_t file2_size = 0; /* for general use, not needed to iterate */
size_t i = 0; /* general counter/iterator */
size_t linemin = 0; /* minimum of comparison lines in file1/2 */
/* read each file into an array of strings,
number of lines read, returned in file_size */
char **file1 = readtxtfile (argv[1], &file1_size);
char **file2 = readtxtfile (argv[2], &file2_size);
linemin = file1_size < file2_size ? file1_size : file2_size;
for (i = 0; i < linemin; i++)
{
size_t nwords1 = 0; /* number of words read file1 line */
size_t nwords2 = 0; /* number of words read file2 line */
size_t wordmin = 0; /* minimum number of words in file1/2 lines */
size_t j = 0; /* general counter/iterator */
printf ("\n file1[%2zu] : %s\n file2[%2zu] : %s\n\n", i, file1[i], i, file2[i]);
char **f1words = splitstr (file1[i], &nwords1);
char **f2words = splitstr (file2[i], &nwords2);
if (!f1words || !f2words) {
fprintf (stderr, "error: word splitting falure.\n");
continue;
}
wordmin = nwords1 < nwords2 ? nwords1 : nwords2;
for (j = 0; j < wordmin; j++)
{
if (strcmp (f1words[j], f2words[j]))
printf (" %16s != %s\n", f1words[j], f2words[j]);
}
free_chararray (f1words);
free_chararray (f2words);
f1words = NULL;
f2words = NULL;
}
/* simple free memory function */
if (file1) free_chararray (file1);
if (file2) free_chararray (file2);
return 0;
}
char** readtxtfile (char *fn, size_t *idx)
{
if (!fn) return NULL; /* validate filename provided */
char *ln = NULL; /* NULL forces getline to allocate */
size_t n = 0; /* max chars to read (0 - no limit) */
ssize_t nchr = 0; /* number of chars actually read */
size_t nmax = NMAX; /* check for reallocation */
char **array = NULL; /* array to hold lines read */
FILE *fp = NULL; /* file pointer to open file fn */
/* open / validate file */
if (!(fp = fopen (fn, "r"))) {
fprintf (stderr, "%s() error: file open failed '%s'.", __func__, fn);
return NULL;
}
/* allocate NMAX pointers to char* */
if (!(array = calloc (NMAX, sizeof *array))) {
fprintf (stderr, "%s() error: memory allocation failed.", __func__);
return NULL;
}
/* read each line from stdin - dynamicallly allocated */
while ((nchr = getline (&ln, &n, fp)) != -1)
{
/* strip newline or carriage rtn */
while (nchr > 0 && (ln[nchr-1] == '\n' || ln[nchr-1] == '\r'))
ln[--nchr] = 0;
array[*idx] = strdup (ln); /* allocate/copy ln to array */
(*idx)++; /* increment value at index */
if (*idx == nmax) /* if lines exceed nmax, reallocate */
array = realloc_char (array, &nmax);
}
if (ln) free (ln); /* free memory allocated by getline */
if (fp) fclose (fp); /* close open file descriptor */
return array;
}
/* split string 's' into separate words including break on
space as well as non-printing and format characters
return pointer to array of pointers to strings 'a' and
number of words in 'n' */
char **splitstr (char *s, size_t *n)
{
if (!s || !*s ) return NULL;
char *p = s; /* pointer to char */
char buf[BUFL] = {0}; /* temporary buffer */
char *bp = buf; /* pointer to buf */
size_t maxs = MAXS; /* check for reallocation */
*n = 0; /* index number of tokens */
/* allocate and validate array of pointer to char */
char **a = calloc (MAXS, sizeof *a);
if (!a) {
fprintf (stderr, "%s() error: memory allocation failed.\n", __func__);
return NULL;
}
while (*p) /* for each char in string1 */
{
/* skip each non-print/format char */
while (*p && (*p <= ' ' || *p > '~'))
p++;
if (!*p) break; /* break if end reached */
while (*p > ' ' && *p <= '~') /* for each printable char */
{
*bp = *p++; /* copy to strings buffer */
bp++; /* advance to nex position */
}
*bp = 0; /* null-terminate strings */
a[*n] = strdup (buf); /* alloc/copy buf to a[*n] */
(*n)++; /* next index in strings */
if (*n == maxs) /* check if *n exceeds maxs */
a = realloc_char (a, &maxs); /* realloc if a if reqd */
bp = buf; /* reset bp to start of buf */
}
return a;
}
/* print an array of character pointers. */
void prn_chararray (char **ca)
{
register size_t n = 0;
while (ca[n])
{
printf (" arr[%3zu] %s\n", n, ca[n]);
n++;
}
}
/* free array of char* */
void free_chararray (char **ca)
{
if (!ca) return;
register size_t n = 0;
while (ca[n])
free (ca[n++]);
free (ca);
}
/* realloc an array of pointers to strings setting memory to 0.
* reallocate an array of character arrays setting
* newly allocated memory to 0 to allow iteration
*/
char **realloc_char (char **p, size_t *n)
{
#ifdef DEBUG
printf ("\n reallocating %zu to %zu (size: %lu)\n", *n, *n * 2, 2 * *n * sizeof *p);
#endif
char **tmp = realloc (p, 2 * *n * sizeof *p);
if (!tmp) {
fprintf (stderr, "%s() error: reallocation failure.\n", __func__);
// return NULL;
exit (EXIT_FAILURE);
}
p = tmp;
memset (p + *n, 0, *n * sizeof *p); /* memset new ptrs 0 */
*n *= 2;
return p;
}
Input Files
$ cat dat/words1.txt
Eye have a spelling chequer,
It came with my Pea Sea.
It plane lee marks four my revue,
Miss Steaks I can knot sea.
Eye strike the quays and type a whirred,
And weight four it two say,
Weather eye am write oar wrong,
It tells me straight aweigh.
Eye ran this poem threw it,
Your shore real glad two no.
Its vary polished in its weigh.
My chequer tolled me sew.
A chequer is a bless thing,
It freeze yew lodes of thyme.
It helps me right all stiles of righting,
And aides me when eye rime.
Each frays come posed on my screen,
Eye trussed too bee a joule.
The chequer pours over every word,
Two cheque sum spelling rule.
$ cat dat/words2.txt
I have a spelling checker,
It came with my Pin See.
It plainly skips marks for my revue,
Mistakes skip I can not see.
I strike the keys and type a word,
And wait for it to say,
Whether I am right or wrong,
It tells me straight away.
I ran this poem through it,
Your are real glad too no.
Its very polished in its way.
My checker told me so.
A checker is a blessed thing,
It frees you lots of time.
It helps me write all styles of writing,
And helps me when I rhyme.
Each pharse composed up on my screen,
I trust too bee a jewel.
The checker pours over every word,
Two check some spelling rule.
Output
$ ./bin/getline_cmplines dat/words1.txt dat/words2.txt
file1[ 0] : Eye have a spelling chequer,
file2[ 0] : I have a spelling checker,
Eye != I
chequer, != checker,
file1[ 1] : It came with my Pea Sea.
file2[ 1] : It came with my Pin See.
Pea != Pin
Sea. != See.
file1[ 2] : It plane lee marks four my revue,
file2[ 2] : It plainly skips marks for my revue,
plane != plainly
lee != skips
four != for
file1[ 3] : Miss Steaks I can knot sea.
file2[ 3] : Mistakes skip I can not see.
Miss != Mistakes
Steaks != skip
knot != not
sea. != see.
file1[ 4] : Eye strike the quays and type a whirred,
file2[ 4] : I strike the keys and type a word,
Eye != I
quays != keys
whirred, != word,
file1[ 5] : And weight four it two say,
file2[ 5] : And wait for it to say,
weight != wait
four != for
two != to
file1[ 6] : Weather eye am write oar wrong,
file2[ 6] : Whether I am right or wrong,
Weather != Whether
eye != I
write != right
oar != or
<snip>
Leak Check
$ valgrind ./bin/getline_cmplines dat/words1.txt dat/words2.txt
==5670== Memcheck, a memory error detector
==5670== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==5670== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==5670== Command: ./bin/getline_cmplines dat/words1.txt dat/words2.txt
==5670==
file1[ 0] : Eye have a spelling chequer,
file2[ 0] : I have a spelling checker,
Eye != I
chequer, != checker,
<snip>
==5670==
==5670== HEAP SUMMARY:
==5670== in use at exit: 0 bytes in 0 blocks
==5670== total heap usage: 330 allocs, 330 frees, 18,138 bytes allocated
==5670==
==5670== All heap blocks were freed -- no leaks are possible
==5670==
==5670== For counts of detected and suppressed errors, rerun with: -v
==5670== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
I am writing a quiz program. The program should read question, answers and correct answer from csv file.
Then it should store them in array.
void read(char question[][50], char answer1[10][10], char answer2[10][10], char answer3[10][10], char answer4[10][10], int correctAnswer[10], int *size, char fileName[], int noOfQuestion){
FILE *reader;
int count;
char qBuffer[50];
char ansBuffer1[50];
char ansBuffer2[50];
char ansBuffer3[50];
char ansBuffer4[50];
int iBuffer = 0;
*size = 0;
//open file
reader = fopen(fileName, "r");
//checking file is open or not
if (reader == NULL)
{
printf("Unable to open file %s", fileName);
}
else
{
fscanf(reader, "%100[^\t*\?,],%[^,],%[^,],%[^,],%[^,],%d", size);
for (count = 0; feof(reader) == 0 && count<*size && count<noOfQuestion; count++){
//Reading file
fscanf(reader, "%100[^\t*\?,],%[^,],%[^,],%[^,],%[^,],%d", qBuffer, ansBuffer1, ansBuffer2, ansBuffer3, ansBuffer4, iBuffer);
//Storing data
strcpy(question[count], qBuffer);
strcpy(answer1[count], ansBuffer1);
strcpy(answer2[count], ansBuffer2);
strcpy(answer3[count], ansBuffer3);
strcpy(answer4[count], ansBuffer4);
correctAnswer[count] = iBuffer;
// Check Correct Number of Items Read
if( count == noOfQuestion )
{
printf("There are more items in the file than MaxNoItems specifies can be stored in the output arrays.\n\n");
*size = count;
}
else if( count != *size - 1 )
{
printf("File is corrupted. Not as many items in the file as specified at the top.\n\n");
*size = count;
}
//Break if reached end of file.
if (feof(reader))
{ break;}
}
fclose(reader);
}
}
This the csv file to read from. each question and answers are in one line.
What function do you use to open a file?,fscanf,fclose,fopen,main,3
Which of the following is not a variable type?,int,float,char,string,4
How many bytes is a character?,8,4,2,1,4
What programming language have you been studying this term?,B,A,D,C,4
Which of the following is a comment?,#comment,//comment,$comment,%comment,2
Which of these is in the C Standard Library?,stdio.h,studio.h,iostream,diskio.h,1
What tool do we use to compile?,compiler,builder,linker,wrench,1
What function do you use to close a file?,fscanf,fclose,fopen,main,2
How do you include a file?,#include,//include,$include,%include,1
What are you doing this quiz on?,paper,whiteboard,computer,chalkboard,3
I worked to find a way to solve the issues in your code, however there just isn't a clean way to follow your double-read of each line an make it work in a reasonable way. The structural issue you have is attempting to read the line twice, first to determine the size and next to try and read the actual values. This has many pitfalls.
Instead of trying to read each line in a piecemeal manner, it is far better to read an entire line at a time using the line-oriented input functions provided by C (fgets, getline). It will make your code much more flexible and make life easier on you as well. The basic approach is to read a line at a time into a 'buffer', then using the tools provided, extract what you need from the line, store it in a way that makes sense, and move on to the next line.
There is just no way to hardcode a bunch of arrays in your function argument list and have it work in a sane way. The proper way to do it is to pass a pointer to some type datastruct to your function, have your function fill it, allocating memory as needed, and provide a pointer in return. In your case a simple structure makes a lot more sense that one two-dimensional array for each question you expect to read.
It is far better to define an initial size for the expected number questions, (MAXQ 128 below), and allocate storage for that amount. You can do the same for expected answers per question (MAXA 16 below). If you end up reading more than each, you can easily reallocate to handle the data.
Once you have your struct filled (or array of structs), you make that data available to your main code by a simple return. You then have a single pointer to your data that you can easily pass you a print function or wherever else you need the data. Since the storage for your data was allocated dynamically, you are responsible for freeing the memory used when it is no longer needed.
I have provided examples of both a print and free function to illustrate passing the pointer to the data between functions as well as the practical printing and freeing of the memory.
Designing your code in a similar manner will save you a lot of headaches in the long run. There are many ways to do this, the example below is simply one approach. I commented the code to help you follow along. Take a look and let me know if you have questions.
Note: I have replaced the original readQAs function with the version I originally wrote, but had a lingering issue with. When using getline you must preserve the starting address for any buffer allocated by getline or repetitive calls to getline will result in a segfault when getline attempts to reallocate its buffer. Basically, getline needs a way of keeping track of the memory it has used. You are free to chop the buffer allocated by getline up any way you want, as long as you preserve the starting address of the originally allocated buffer. Keeping a pointer to the original is sufficient.
This can be particularly subtle when you pass the buffer to functions that operate on the string such as strtok or strsep. Regardless, the result of failing to preserve the start of the buffer allocated by getline will result in a segfault at whatever loop exhausts the initial 120-byte buffer allocated by getline receiving __memcpy_sse2 () from /lib64/libc.so.6 If you never exhaust the original 120-byte buffer, you will never experience a segfault. Bottom line, always preserve the starting address for the buffer allocated by getline.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXQ 128
#define MAXA 16
typedef struct {
char *q;
char **ans;
unsigned int nans;
} ques;
ques **readQAs (char *fn);
void prn_ques (ques **exam);
void free_ques (ques **exam);
int main (int argc, char **argv) {
if (argc < 2) {
fprintf (stderr,"\n error: insufficient input. Usage: %s <csvfile>\n\n", argv[0]);
return 1;
}
ques **exam = NULL; /* pointer to pointer to struct */
/* allocate/fill exam structs with questions/answers */
if ( !( exam = readQAs (argv[1]) ) ) {
fprintf (stderr, "\n error: reading questions/answers from '%s'\n\n", argv[1]);
return 1;
}
prn_ques (exam); /* print the questions/answers */
free_ques (exam); /* free all memory allocated */
return 0;
}
/* allocate and fill array of structs with questions/answers */
ques **readQAs (char *fn)
{
FILE *fp = fopen (fn, "r"); /* open file and validate */
if (!fp) {
fprintf (stderr,"\n error: Unable to open file '%s'\n\n", fn);
return NULL;
}
char *line = NULL; /* line buff, if NULL getline allocates */
size_t n = 0; /* max chars to read (0 - no limit) */
ssize_t nchr = 0; /* num chars actually read by getline */
char *p = NULL; /* general pointer to parse line */
char *sp = NULL; /* second pointer to parse line */
char *lp = NULL; /* line ptr (preserve line start addr) */
size_t qidx = 0; /* index for questions structs */
size_t aidx = 0; /* index for answers within structs */
ques **q = calloc (MAXQ, sizeof (*q)); /* allocate MAXQ ptrs */
if (!q) { fprintf (stderr,"\n Allocation error.\n\n"); return NULL; }
/* for each line in file (fn) */
while ((nchr = getline (&line, &n, fp)) != -1)
{
/* test qidx = MAXQ-1, realloc */
aidx = 0; /* reset ans index each line */
lp = line; /* save line start address */
if (line[nchr - 1] == '\n') /* test/strip trailing newline */
line[--nchr] = 0;
q [qidx] = calloc (1, sizeof (**q)); /* allocate struct */
q [qidx]-> ans = calloc (MAXA, sizeof (*(q[qidx]-> ans)));
/* read question */
*(p = strchr (line, ',')) = 0; /* null-terminate ln at ',' */
q [qidx]-> q = strdup (line); /* alloc/read question */
sp = p + 1; /* sp now starts next ch */
/* read correct answer number */
*(p = strrchr (sp, ',')) = 0; /* null-term ln at last ',' */
q [qidx]-> nans = *(p+1) - '0'; /* save num ans, cvt to %zd */
/* read multi-choice answers */
for (p = strtok (sp, ","); p && *p; p = strtok (NULL, ","))
q [qidx]-> ans [aidx++] = strdup (p); /* alloc/read ans */
line = lp; /* avoid __memcpy_sse2 err */
qidx++; /* inc index for next Q */
}
if (line) free (line); /* free line memory */
if (fp) fclose (fp); /* close file stream */
return q; /* return ptr to array of structs holding Q/A(s) */
}
/* print formatted exam read from file */
void prn_ques (ques **exam)
{
if (!exam) {
fprintf (stderr, "\n %s() error: invalid exam pointer.\n\n", __func__);
return;
}
size_t qidx = 0; /* index for questions structs */
size_t aidx = 0; /* index for answers within structs */
printf ("\nClass Exam\n\n");
while (exam [qidx])
{
printf (" %2zd. %s\n\n", qidx + 1, exam[qidx]-> q);
aidx = 0;
while (exam[qidx]->ans[aidx])
{
if (exam[qidx]-> nans == aidx + 1)
printf ("\t(%c) %-16s (* correct)\n", (int)aidx + 'a', exam[qidx]->ans[aidx]);
else
printf ("\t(%c) %s\n", (int)aidx + 'a', exam[qidx]->ans[aidx]);
aidx++;
}
printf ("\n");
qidx++;
}
printf ("\n");
}
/* free all memory allocated */
void free_ques (ques **exam)
{
if (!exam) {
fprintf (stderr, "\n %s() error: invalid exam pointer.\n\n", __func__);
return;
}
size_t qidx = 0; /* index for questions structs */
size_t aidx = 0; /* index for answers within structs */
while (exam[qidx])
{
if (exam[qidx]->q) free (exam[qidx]->q);
for (aidx = 0; aidx < MAXA; aidx++) {
if (exam[qidx]->ans[aidx]) {
free (exam[qidx]->ans[aidx]);
}
}
free (exam[qidx]->ans);
free (exam[qidx++]);
}
free (exam);
}
output/verification:
$ ./bin/readcsvfile dat/readcsvfile.csv
Class Exam
1. What function do you use to open a file?
(a) fscanf
(b) fclose
(c) fopen (* correct)
(d) main
2. Which of the following is not a variable type?
(a) int
(b) float
(c) char
(d) string (* correct)
3. How many bytes is a character?
(a) 8
(b) 4
(c) 2
(d) 1 (* correct)
4. What programming language have you been studying this term?
(a) B
(b) A
(c) D
(d) C (* correct)
5. Which of the following is a comment?
(a) #comment
(b) //comment (* correct)
(c) $comment
(d) %comment
6. Which of these is in the C Standard Library?
(a) stdio.h (* correct)
(b) studio.h
(c) iostream
(d) diskio.h
7. What tool do we use to compile?
(a) compiler (* correct)
(b) builder
(c) linker
(d) wrench
8. What function do you use to close a file?
(a) fscanf
(b) fclose (* correct)
(c) fopen
(d) main
9. How do you include a file?
(a) #include (* correct)
(b) //include
(c) $include
(d) %include
10. What are you doing this quiz on?
(a) paper
(b) whiteboard
(c) computer (* correct)
(d) chalkboard
valgrind verification:
==16221==
==16221== HEAP SUMMARY:
==16221== in use at exit: 0 bytes in 0 blocks
==16221== total heap usage: 73 allocs, 73 frees, 3,892 bytes allocated
==16221==
==16221== All heap blocks were freed -- no leaks are possible
==16221==
==16221== For counts of detected and suppressed errors, rerun with: -v
==16221== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)