So I am coming from C++ for the first time with this exercise using pointers. My professor would like us to do this exercise without using index to have better mastery of pointers. I'm trying to read from a file into a 2d array embedded in a dynamically allocated struct. I hope I've worded my question well enough and provided the proper information. Thank you very much.
my struct
typedef struct //struct that holds ASCII art
{
char artistName[80]; //name of artist
char asciiArt[20][80]; //actual ascii art line by line
int rating; //rating of art
}TextArt;
Array of struct declaration and allocation
TextArt **artPtr;
artPtr = malloc(1000 * sizeof(TextArt) + 1);
Line of code giving me problems. I read from the file fine up to this point, I'm not sure if this while is valid, I'm looking for a # in the first character. The syntax is very confusing for me here not using index, so this is where I need the most help. It is a pointer to an array of struct (*asciiArt + i) then in the struct -> a 2d c string array asciiArt[][].
while(*tempLine != '#') //while there is no # in position [0] -- read into temp string
{
printf("while templine\n");
fgets((*asciiArt+i)->asciiArt, 100, pFile);
}
Full function from above code if you need more info, otherwise ignore this block of code.
void readFromFile(TextArt **asciiArt, int size, char *fileName)
{
int index = 0; //index for array of struct
int i = 0;
int j = 0;
char tempLine[500]; //temp placeholder
FILE *pFile;
pFile = fopen (fileName ,"r");
if (pFile == NULL)
printf("Error opening file");
else
{printf("file opened.");
for(i = 0; pFile != NULL; i++) //*(*(data + i) + j)
{ j=0;
fgets((*asciiArt+i)->artistName, 100, pFile); //get artist name from first line
printf("got artist name");
while(*tempLine != '#')
{printf("while templine\n");
fgets((*asciiArt+i)->asciiArt, 100, pFile); //while there is no # -- read into temp string
}
strcpy(tempLine, ""); //clear temp string
printf("for loop done");
}
return;
}
}
This is a mistake:
TextArt **artPtr;
artPtr = malloc(1000 * sizeof(TextArt) + 1);
sizeof(TextArt) has nothing to do with sizeof(TextArt *). artPtr points to an array of pointers, not to an array of TextArt objects. Each of those 1000 pointers you just allocated (or tried to allocate) currently points nowhere, and you have to point each one somewhere before using them.
Update: OP clarified the intent to allocate an array of 1000 TextArts:
TextArt *artPtr;
artPtr = malloc(1000 * sizeof(TextArt));
I'm not sure what the + 1 was meant to be.
If the readFromFile function needs to possibly resize the array then you pass it like:
void readFromFile( &artPtr, ......
and inside the function you access it like:
fgets((*asciiArt)[i].artistName, 100, pFile);
Actually I'd write
TextArt *artPtr = *asciiArt;
fgets( artPtr[i].artistName, 100, pFile );
as that is easier to read instead of having brackets everywhere.
If the function does not need to reallocate then just make it take TextArt *artPtr;.
Probably
TextArt *artPtr;
artPtr = malloc(1000 * sizeof(TextArt));
...
void readFromFile(TextArt *asciiArt, int size, char *fileName){
int index, i, ch; //index for array of struct
char tempLine[500];
FILE *pFile;
pFile = fopen (fileName ,"r");
if (pFile == NULL)
printf("Error opening file");
else{
printf("file opened.");
for(index = 0; index < size; ++index){
TextArt *pta = asciiArt + index;
fgets(pta->artistName, sizeof(pta->artistName), pFile);
printf("got artist name");
for(i=0;i<20;++i){//20 : sizeof(pta->asciiArt)/sizeof(pta->asciiArt[0])
if(EOF==(ch=fgetc(pFile))){
index = size;//outer loop break
break;
} else if(ch == '#'){
//ungetc(ch, pFile);
fgets(tempLine, sizeof(tempLine), pFile);
break;
} else {
ungetc(ch, pFile);
fgets(pta->asciiArt + i, sizeof(pta->asciiArt[0]), pFile);
}
}
}
fclose(pFile);
}
}
Related
I am reading a file that contains several lines of strings(max length 50 characters). To store those strings I created a char double-pointer using calloc. The way my code works is as it finds a line in the file it adds one new row (char *) and 50 columns (char) and then stores the value.
My understanding is that I can call this method and get this pointer with values in return. However, I was not getting the values so I check where I am losing it and I found that the memory is not persisting after while loop. I am able to print strings using print 1 statement but print 2 gives me null.
Please let me know what I am doing wrong here.
char **read_file(char *file)
{
FILE *fp = fopen(file, "r");
char line[50] = {0};
char **values = NULL;
int index = 0;
if (fp == NULL)
{
perror("Unable to open file!");
exit(1);
}
// read both sequence
while (fgets(line, 50, fp))
{
values = (char **)calloc(index + 1, sizeof(char *));
values[index] = (char *)calloc(50, sizeof(char));
values[index] = line;
printf("%s",values[index]); // print 1
index++;
}
fclose(fp);
printf("%s", values[0]); // print 2
return values;
}
line content is overwritten on each loop iteration (by fgets()).
values is overwritten (data loss) and leaks memory on each iteration index > 1.
value[index] is allocated memory on each iteration which leaks as you overwrite it with the address of line on the following line.
line is a local variable so you cannot return it to caller where it will be out of scope.
caller has no way to tell how many entries values contain.
Here is a working implementation with a few changes. On error it closes the file and frees up memory allocated and return NULL instead of exiting. Moved printf() to caller:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUF_LEN 50
char **read_file(char *file) {
FILE *fp = fopen(file, "r");
if(!fp) {
perror("Unable to open file!");
return NULL;
}
char **values = NULL;
char line[BUF_LEN];
unsigned index;
for(index = 0;; index++) {
char **values2 = realloc(values, (index + 1) * sizeof(char *));
if(!values2) {
perror("realloc failed");
goto err;
}
values = values2;
if(!fgets(line, BUF_LEN, fp)) break;
values[index] = strdup(line);
}
fclose(fp);
values[index] = NULL;
return values;
err:
fclose(fp);
for(unsigned i = 0; i < index; i++) {
free(values[i]);
}
free(values);
return NULL;
}
int main() {
char **values = read_file("test.txt");
for(unsigned i = 0; values[i]; i++) {
printf("%s", values[i]);
free(values[i]);
}
free(values);
return 0;
}
fgets() returns line ending in '\n' or at most BUF_LEN - 1 of data. This means a given value[i] may or may not be ending with a \n. You may want this behavior, or you want value[i] to be consistent and not contain any trailing \n irregardless of the input.
strdup() is _POSIX_C_SOURCE >= 200809L and not standard c,
so if you build with --std=c11 the symbol would not be defined.
I have written a struct array to a binary file using this function:
int write_binary(const char* filename, const Product* shop)
{
FILE* OUT;
int jees = 0;
int i = 0;
OUT = fopen(filename, "wb");
if (!OUT) {
return 1;
}
while (jees == 0)
{
//the last element of the struct array has '\0' as the first char of its name
if (shop[i].name[0] == '\0')
{
jees = 1;
}
fwrite(&shop[i], sizeof (Product), 1, OUT) ;
i++;
}
fclose(OUT);
return 0;
}
Now I want to read it back into a struct array pointer. I have tried:
Product* read_binary(const char* filename)
{
FILE* IN = fopen(filename,"rb");
Product *shop;
for (int i = 0; i < 10; i++) {
fread(&shop[i], sizeof(Product), 1, IN);
}
fclose(IN);
return shop;
}
But this way doesn't seem to work. Is there a way to find out the how many structs are in the binary data?
Product *shop;
Here you are declaring a pointer, but you are not allocating memory for it. You should allocate with malloc() or do some static allocation.
To know the number of structs in the file, I'd seek to the end of it, count the bytes and divide by the size of the struct.
A side note: you don't need the jees variable. Just test the breaking condition after writing and break the loop explicitly:
for (i = 0; ; i++)
{
fwrite(&shop[i], sizeof (Product), 1, OUT);
if (shop[i].name[0] == '\0')
break;
}
Given the well-formatted text file called input.txt below:
Yesterday snowed
Today is hot
Tomorrow will rain
Next week will earthquake
How can I read the text file line by line and also dynamically allocate memory to each English word as a character array if I do not know the length of each English word since I do not want to waste 1000 bytes on a short word. Should realloc be used in this case? The following is my code:
int main() {
FILE* pfile = fopen("input.txt", "r");
int i = 0;
while (i != 0) {
char* stringLiteral = (char*) malloc(1000 * sizeof(char));
i = fscanf(pfile, "%s", stringLiteral);
insertString(stringLiteral);
}
fclose("input.txt");
return 1;
}
void insertString(char* charArray) {
/*This function inserts a char array to a linked list*/
}
If you want you can use realloc, yes, in that case you would need to reallocate, smaller pieces of memory.
You can even reallocate char by char stretching the string as it's being populated and not waste a single byte.
Example with comments:
Live demo
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *pfile = fopen("input.txt", "r");
if (pfile == NULL) { //check for errors in opening file
perror("fopen");
}
else {
int c;
int i = 0; //string iterator
char *stringLiteral;
stringLiteral = malloc(1); //initial allocation
if(stringLiteral == NULL) {
perror("malloc");
return EXIT_FAILURE;
}
while ((c = fgetc(pfile)) != EOF) { //until the end of the file is reached
if (c != '\n') { //until the line ends
stringLiteral = realloc(stringLiteral, i + 1); //keep reallocating memory for each character
if(stringLiteral == NULL){
perror("malloc");
return EXIT_FAILURE;
}
stringLiteral[i] = c; //assing the read character to the char array
i++;
}
else { //'\n' was reached
stringLiteral[i] = '\0'; //terminate string
//insertString(stringLiteral); //your insertion function
printf("%s\n", stringLiteral); //test print
i = 0;
}
}
//insertString(stringLiteral); //last read line
printf("%s\n", stringLiteral); // test print
fclose(pfile);
}
return EXIT_SUCCESS;
}
The problem here is that memory allocation is an expensive process and can slow down your program.
You have to weigh what's more important, the space or the speed. Unless the strings are so huge that they cannot fit in the stack, in that case memory allocation is the way to go, though it can be more sensible to allocate blocks of bytes instead of byte by byte.
I am reading words from a file, exact number 2243. And I am trying to store them inside array so I can read them later on the program. Code is inside function. The file doesn't have sentences, just words one below the other.
char** fill_word_array(char* filename){
int i = 0, j = 0;
int lines = 0;
char str[20];
char *array[i][j];
array[i][j] = malloc(lines * sizeof(char*));
FILE * fp = fopen("words.txt", "r");
if (fp == NULL)
{
printf("Cannot open file");
return 0;
}
while (fscanf(fp, "%s", str) != EOF)
{
lines++;
}
printf("%d\n", lines);
fseek(fp, 0, SEEK_SET);
array[i][j] = malloc(sizeof(char*) * lines);
for(i = 0; i <= lines; i++)
{
strcpy(array[lines][j], str);
printf("%s", array[i][j]);
}
return 0;
}
This code prints only the last word of the file as shown here http://tinypic.com/r/2zhjmgx/9.
A declaration like char *array[i][j] with i==0 and j==0 will reserve an array of size 0, such that even position array[0][0] is not legally accessible (undefined behaviour). So when you write array[i][j] = malloc(lines * sizeof(char*)), you already write to array[0][0] yielding UB.
Usually one gets punished by the community for providing the solution to somebody asking for help and thereby steeling him the chance to make the experiences on his own. For your maturity level, however, it seems to me that the exercise is to hard, and therefore I'll provide a solution following your approach and describe the things that are to consider.
Hope it helps.
With your approach you need to...
count the words
allocate space for "words * sizeof(char*)" +
one word to store a NULL-pointer at the end (otherwise the users
of your result will not know where to stop).
rewind and read in every word in a loop;
in the loop, make a copy of the temporary word content and store it in the array
after the loop, write the
final NULL-pointer.
return the array (and tell the user that he
will have to free memory later)
Here's the code:
char** fill_word_array(char* filename){
int words = 0;
char str[100];
char **array;
FILE * fp = fopen("words.txt", "r");
if (fp == NULL)
{
printf("Cannot open file");
return 0;
}
while (fscanf(fp, "%s", str) != EOF)
{
words++;
}
printf("%d\n", words);
fseek(fp, 0, SEEK_SET);
array = malloc(sizeof(char*) * (words+1));
for(int i = 0; i < words && fscanf(fp, "%s", str) != EOF; i++)
{
array[i] = strdup(str);
}
array[words] = NULL;
return array;
}
int main() {
char **array = fill_word_array("someFile.txt");
for (int i=0; array[i] != NULL; i++) {
printf("%s\n", array[i]);
free(array[i]);
}
free(array);
}
I have a filed called a1.txt which contains the words
amazing
malevolent
permanent
and another one called a2.txt with
Amazing
Bridge
Malevolent
Here is the code that I use to read the files into arrays, thanks to #M Oehm.
NOTE: void b(); is the same as void a() but it reads a2.txt instead.
void a();
void b();
char (*a1)[50];
char (*a2)[50];
int n;
int main(int argc, char *argv[]) {
a();
printf("\n\n");
b();
int i=0, j=0;
for (i; i < strlen(*a1); i++)
{
for (j; j <strlen(*a2); j++)
{
printf("\n%d", strcmp(a1[i], a2[j]));
}
}
return 0;
}
void a(){
FILE *f;
int i;
f = fopen("a1.txt", "r");
if (f == NULL) {
fprintf(stderr, "Can't open file\n");
exit(1);
}
/* first pass */
n = 0;
while (fscanf(f, "%*s") != EOF) n++; /* star means: scan, but don't store */
a1 = malloc((n + 1) * sizeof(*a1));
if (a1 == NULL) {
fprintf(stderr, "Allocation failed\n");
exit(1);
}
/* second pass */
fseek(f, 0, SEEK_SET);
for (i = 0; i < n; i++) {
fscanf(f, "%49s", a1[i]);
}
*a1[n] = '\0';
/* process words */
for (i = 0; i < n; i++) {
printf("%s\n",a1[i]);
}}
As you can see the rows of the arrays are dynamic(I used three words as a test, however this should be done for an unknown amount of words hence the usage of calloc). Is it possible to detect the rows of each array and write the common words of each in a new file?
Finding the common words is a simple matter, I assume, of using strstr.
You seem to have some misconceptions about memory allocation:
char *str[50] creates an array of 50 (uninitialised) pointers of char. Perhaps you want char (*str)[50], which is a pointer to an array of 50 chars, to which you can allocate memory.
lSize is the length of the file, i.e. the number of chars. It looks a bit as if you wanted to count the number of words.
I'll present two strategies for reading words into a char array.
Read fixed-size words
This strategy uses a fixed word size of 50, as in your example. It opens the file and reads it in two passes. The first to determine the number of words, the next to read the actual words after allocating enough space.
int main(int argc, char *argv[])
{
FILE *f;
char (*str)[50]; /* Pointer to words of max length 49 */
int n; /* number of words */
int i;
if (argc != 2) {
fprintf(stderr, "Usage: $fifo file_name.ip\n");
exit(1);
}
f = fopen(argv[1], "r");
if (f == NULL) {
fprintf(stderr, "Can't open file\n");
exit(1);
}
/* first pass */
n = 0;
while (fscanf(f, "%*s") != EOF) n++; /* star means: scan, but don't store */
str = malloc((n + 1) * sizeof(*str));
if (str == NULL) {
fprintf(stderr, "Allocation failed\n");
exit(1);
}
/* second pass */
fseek(f, 0, SEEK_SET);
for (i = 0; i < n; i++) {
fscanf(f, "%49s", str[i]);
}
*str[n] = '\0';
/* process words */
for (i = 0; i < n; i++) {
printf("%4d: '%s'\n", i, str[i]);
}
free(str);
return 0;
}
This approach is reasonably simple, but it has two drawbacks: You will waste memory, because most words won't be 50 characters long. And you have to scan the file twice. Both drawbacks are not serious on modern computers.
Allocate as you go
You can also maintain the words as pointers to pointers to char, char **str. str[i] gives you a word, which is stored as pointer into existing memory of a null-terminated string. The function strtok gives you such strings.
This "existing memory" is the contents of the file as char buffer. Rohan has shown you how to get ti: By getting the file length, allocating and reading.
This method takes only one pass, because it reallocates memory according to its needs. Start with space for, say, 64 words, read them, find out we need more, so reallocate to make 128 words fit, read words 64-127, and so on.
int main(int argc, char *argv[])
{
FILE *f;
char *buf; /* Buffer that hold the file's contets */
size_t size; /* Size of that buffer */
char **str; /* Array of pointers to words in that buffer */
int n; /* number of words */
int nalloc; /* For how many words space is allocated */
int i;
if (argc != 2) {
fprintf(stderr, "Usage: $fifo file_name.ip\n");
exit(1);
}
f = fopen(argv[1], "r");
if (f == NULL) {
fprintf(stderr, "Can't open file\n");
exit(1);
}
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
buf = malloc(size + 1);
if (buf == NULL) {
fprintf(stderr, "Allocation failed\n");
exit(1);
}
/* read whoe file */
fread(buf, 1, size, f);
buf[size] = '\0';
fclose(f);
n = 0;
nalloc = 0;
str = NULL;
for (;;) {
if (n >= nalloc) {
/* reallocate */
nalloc = nalloc * 2;
if (nalloc == 0) nalloc = 64;
str = realloc(str, nalloc * sizeof(*str));
if (str == NULL) {
fprintf(stderr, "Reallocation failed\n");
exit(1);
}
}
str[n] = strtok(n ? NULL : buf, " \t\n\r");
if (str[n] == NULL) break;
n++;
}
/* process words */
for (i = 0; i < n; i++) {
printf("%4d: '%s'\n", i, str[i]);
}
free(buf);
free(str);
return 0;
}
This approach is more efficient, but also more complicated. Note how many variables I need to keep track of everything: The llocated size, the actual size, the size of the text buffer. And I have to take care of two allocated arrays.
Given that you want to read two files, it makes sense to pack these variables into a structure and read each file into such a structure.
Conclusion
These are only two of many ways to read words from a file. Both are not trivial and require that you understand how to manage memory.
I think one of the most basic things to learn is that a pointer may be used for many different things. It can just point to existing memory, whether that has been allocated or is an automatic array. But it can also be used as a handle to allocated memory; it will then behave like an array, excapt that you have to free the memory after use. You should not "move" such pointers, i.e. change the address they point to.
Both kinds of pointers look the same in your code, but you have to know which pointer acts as what.
With
char *a1[50];
char *a2[50]; //not used so can remove
You are creating array of char pointers, not array of characters. You may want to just use char pointers as
char *a1;
char *a2;
Then instead of
a1[50] = calloc(1, lSize +1);
do
a1 = calloc(1, lSize +1);
Using a1[50] as in your code is incorrect and will cause undefined behavior (including segmentation fault). The array elements are from 0 to 49, so last element is a1[49].
Also, you can use lSize to read those many characters as below
for (i=0; i <lSize; i++)
{
if (fscanf(file, "%c", &a1[i]) == 1){
printf("%c", a1[i]);
}
}
But may be you can skip the for loop limit and read from file until there is no error.