I want to take the contents from a file and print them out in a random order. Right now I am just trying to take the contents from the file and printing it back out. for example right now the file has:
0 Abdelmeged Zane
1 Attri Sonal
And it prints out the second line not the first.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[]){
FILE *file;
char string[50];
char array[100];
int i = 0;
char *p;
file = fopen(argv[1], "r");
if(file == NULL) {
perror("Error opening file");
return(-1);
}
printf("\nOriginal Array: \n");
while(fgets(string, 100, file)!= NULL) {
printf(string);
p = &array[i];
strcpy(p, string);
i++;
}
i = 0;
printf("\nShuffled Array: \n");
while(i < 20){
printf("%c", array[i]);
i++;
}
fclose(file);
return(0);
}
The problem with your code is you declare char array[100]; and then essentially overwrite the string you store in it with strcpy(p, string); with each iteration of your while loop. There is no need to use a pointer p in this case. What you need to do is allocate multiple pointers to hold multiple strings. The normal way is to allocate some number of pointers (10 below -- see MAXLINES), and then allocate space for each string as you use each pointer. The following is a quick example (note there are many different ways to do this). Also, since you are allocating memory, you are responsible for freeing it as well (also shown below). Drop a comment if you have questions.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXLINES 10
int main(int argc, char* argv[]){
FILE *file;
char string[50];
char **array = NULL;
int i = 0;
if (argc < 2) {
fprintf (stderr, "Error: insufficient input. Usage: %s <filename>\n", argv[0]);
return 1;
}
file = fopen(argv[1], "r");
if(file == NULL) {
perror("Error opening file");
return 1;
}
array = calloc (MAXLINES, sizeof (*array)); /* MAXLINES char pointers */
if (!array) {
fprintf (stderr, "Error: calloc pointer allocation failed\n");
return 1;
}
printf("\nOriginal Array: \n");
while(fgets(string, 100, file)!= NULL) {
printf(" %s", string);
array[i] = strdup (string); /* strdup allocates space for string */
i++;
}
fclose(file); /* close it here, you are done with it */
i = 0; /* print array stored in memory */
printf("\nShuffled Array: \n");
while (array[i])
{
printf(" %s", array[i]);
i++;
}
i = 0;
while (array[i]) /* free memory dynamically allocated: */
{
free (array[i]); /* free array/string memory */
i++;
}
if (array) free (array); /* free pointer allocation */
return 0;
}
Note: you should also save the number of strings read after your loop with fgets (e.g. int numlines = i;). Do this before you reset i = 0;. That way you have a convenient reference to the number of strings to use in your shuffle. (you can also always use the while (array[i]) loop to count them again, but why?
input file:
$ cat dat/abde.txt
0 Abdelmeged Zane
1 Attri Sonal
output:
$ ./bin/shufs dat/abde.txt
Original Array:
0 Abdelmeged Zane
1 Attri Sonal
Shuffled Array:
0 Abdelmeged Zane
1 Attri Sonal
You are copying the string you read into the second char array, offsetting the start point by one each time. So at the end, assuming you don't have a buffer overflow, then your array will hold the first character of each line up to the last, which it will hold in its entirety.
Depending where you are in your study, either create a static array of arrays of chars to hold each line, or an array of pointers and malloc the copy you save.
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'm attempting to copy a C-string, which is read in from a file to an element of a struct array, but it is not copying. When I attempt to print, the word is not there. I'm kind of new to C. Below is my code. Many thanks for your help.
typedef struct Tree{
int numTimes; //number of occurrences
char* word; //the word buffer
}Node;
#include "proj2.h"
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]){
FILE* readIn; //read file pointer
FILE* writeOut; //write file pointer
char buffer[18]; //allocate buffer ***please do not fuzz
int length = 0;
int count = 0;
Node* array = (Node*) malloc(sizeof(Node));
/*if(argc < 3){ //if the number of command line arguments is < 3, return EXIT_FAILURE
return EXIT_FAILURE;
}*/
argv[1] = "/Users/magnificentbastard/Documents/workspaceCPP/proj2/Password.txt"; //****testing
argv[2] = "outFile.txt"; //****testing
readIn = fopen(argv[1], "r"); //opens the selected argument file for reading
writeOut = fopen(argv[2], "w"); //opens the selected argument file for writing
if(readIn == NULL){ //if there
printf("ERROR: fopen fail.\n");
return EXIT_FAILURE; //exits if the file opens
}
while(fscanf(readIn, "%18s", buffer) == 1){ //loop to read in the words to the buffer
count++; //counts the words coming in
modWord(buffer); //modifies the words coming in
array = (Node*)realloc(array, sizeof(Node));
for(int i = 0; i < count; i++){ //****not copying over...HELP
strcpy(array[i].word, buffer);
}
}
//Node array[count];
fprintf(stderr, "%d ", count); //***for testing purposes only
int elements = sizeof(array)/sizeof(array[0]); //***testing assigns num elements
fprintf(stderr, "%d ", elements); //***testing prints num elements
fclose(readIn); //closes the in-file
fclose(writeOut); //closes the out-file
return EXIT_SUCCESS;
}
array[count] doesn't allocate the memory. I believe what you're trying to implement here is single-linked list of strings.
What you're trying to do can be achieved, but you'd need to allocate memory for array by using malloc/free combo. What's more, what you're trying to achieve should by done by either making Node.word an array of fixed size OR a pointer and allocating the memory on Node-by-Node basis.
Length of an array cannot be retrieved by use of sizeof operator as sizeof is evaluated in compile and it'll always return a size of a pointer on your platform.
I am trying to write a program that takes the words from a file, and puts those in a dynamic array. However when I try to run my code the program copies it all except for the spaces. How do I fix this?
This is a test does it work?
But I get the following:
Thisisatestdoesitwork?
char** getWords(char* filename, int* pn){
char** tmp = (char**)malloc( 1000*sizeof(char));
int *temp=(int*)malloc(1000*sizeof(int);
int c;
int counter = 0;
FILE* fileInput = fopen(filename, "r");
if(fileInput == NULL){
return tmp; // return if file open fails
}
while((c=fgetc(fileInput)) != EOF){
result = fscanf(fileInput, "%c", &c); //try to read a character
if(isalpha(c)){ //chararect alphabetical
tmp[counter] = c; // safe int to array
counter ++;
printf("%c", c); fflush(stdout);
}
else{ // if read not succesfull
fscanf(fileInput, ""); // needs to scan anything not a character
}
if(counter > 100){ // to not exceed the array
break;
}
if(feof(fileInput)){ // to check if at the end of the file
break;
}
}
fclose(fileInput); // closing file
*pn = counter;
return tmp;}
My main Function:
int main(int argc, char* argv[]){
int n;
char** a = getWords("opdracht_4_5.c", &n);
if (a != NULL){
puts("gevonden woorden:");
for (int i = 0;i < n; i++){
printf("%3d %s\n",i,a[i]);
}
for (int i = 0;i < n; i++){
free(a);
}
free(a);
}
return (EXIT_SUCCESS);
}
There are quite a few problems with your code. Here's a start:
You don't test the return value of fopen().
You don't test the return value of malloc().
You assign the return value of fgetc() to a variable of type char. Plain char is compatible with either signed char or unsigned char. In order to make a distinction between a character and EOF (which is negative), the fgetc() function returns a character converted to unsigned char (or EOF). You need to test for EOF and then convert the value to a plain char.
The is...() function expects an int argument whose value is in the range of an unsigned char or EOF. If you have a plain char, you first have to cast it to unsigned char, or you can pass the return value of fgetc() straight to isalpha().
You attempt to append an zero-length char array (temp) to an uninitialized char array (s), and you do not test if there is enough room in the target array. This is broken for more reasons than than I care to enumerate.
You allocate memory for an array of 1000 pointers to char, but you never allocate memory for the char pointers themselves.
You try to append your buffer (s) to an uninitialized pointer (*tmp).
You call strlen() on something that is not null-terminated.
You never return the length of the array.
You call a number of functions that have not been declared.
This will read the file, put each word in an array
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
char** getWords(char* filename, int* pn){
char input[100]; // array to hold each word
char** tmp; // double pointer
int counter = 0;
int words = 0;
int c;
tmp = malloc( (*pn)*sizeof(char*)); // allocate pointers for number of words
if ( tmp == NULL) {
printf ( "malloc failed\n");
exit (1);
}
FILE* fileInput = fopen(filename, "r");
if(fileInput == NULL){
printf ( "file open failed\n");
*pn = 0; // no words entered
return tmp; // return if file open fails
}
while(( c = fgetc(fileInput)) != EOF){
if( isalnum(c)){ // is alpha or number
input[counter] = c; // save to array
input[counter + 1] = '\0'; // save a \0 to the end to make a string
counter ++;
}
else{ // not alpha or number
if ( counter > 0) { // if there are characters, save the word
tmp[words] = malloc ( strlen ( input) + 1); // memory for this word
strcpy ( tmp[words], input); // copy the word to the array
words++;
counter = 0;
if ( words >= *pn) { // got all the words wanted
break;
}
}
}
if(counter > 98){ // too many characters for input, start a new word
tmp[words] = malloc ( strlen ( input) + 1);
strcpy ( tmp[words], input);
words++;
counter = 0;
if ( words >= *pn) {
break;
}
}
}
fclose(fileInput); // closing file
*pn = words; // save number of words
return tmp;
}
int main(int argc, char* argv[]){
int n;
int i;
printf ( "enter the number of words to obtain\n");
scanf ( "%d", &n);
char** a = getWords("opdracht_4_5.c", &n);
if (a != NULL){
puts("gevonden woorden:");
for ( i = 0;i < n; i++){
printf("%3d %s\n",i,a[i]);
}
for ( i = 0;i < n; i++){
free(a[i]); // free each word
}
free(a); // free the pointer to the words
}
return (EXIT_SUCCESS);
}
The input file I used had these as the first two lines
#include<stdio.h>
#include<string.h>
I get this output:
enter the number of words to obtain
6
gevonden woorden:
0 include
1 stdio
2 h
3 include
4 string
5 h
This answer is as yet incomplete
Please allow me to finish this before commenting on it -- Thank you
There are a lot if issues with your code, I won't clean it up for you. However I would like to give you some hints on how your program SHOULD be coded:
Your main objective is to read a file and load the content word by word in an array.
Sorting is an incorrect use because that implies you want to sort them alphabetically or in some other order after loading it into an array.
Okay, so first things first, let's figure out the overall operation of our program. We'll call our program kitten, because it's not quite as powerful as cat.
To run our program we will assume that we give it the filename we want to read on the command-line as follows:
$ ./kitten somefile.txt
and expect the output to be:
word1
word2
word3
.
.
.
wordN
Total words: N
So, let's get started, first we make sure that our user specifies a filename:
#include <stdio.h>
int usage(const char *progname);
int main(int argc, char *argv[])
{
if (argc < 2) {
usage(argv[0]);
return -1;
}
return 0;
}
int usage(const char *progname)
{
fprintf(stderr, "Usage is:\n\t%s filename\n", progname);
}
Now that we know that our program can get a filename, let's try to open the text file, if there is an issue with it we use perror to display the error and exit the program, otherwise we are ready to use the file:
#include <stdio.h>
#include <errno.h>
int usage(const char *progname);
int main(int argc, char *argv[])
{
FILE *fp;
if (argc < 2) {
usage(argv[0]);
return -1;
}
fp = fopen(argv[1], "r");
if (!fp) {
perror(argv[1]); /* display system error, with the filename */
return -1;
}
/* TODO: file manipulation goes here */
fclose(fp); /* close the file */
return 0;
}
int usage(const char *progname)
{
fprintf(stderr, "Usage is:\n\t%s filename\n", progname);
}
Now in C each function should perform just one task. The task should make human sense. For example if the function is supposed to read words into an array, then that's all it should do, it should not open a file or close a file, which is WHY the code above does not create a function for opening the file the way you did. Your function should take in FILE * as the file to read.
Because we use the FILE * as input we'll start the function name with an f to keep with the stdio convention. Ideally, the function should take a pointer to char * (strings) to store the words in.
#include <stdio.h>
#include <errno.h>
int usage(const char *progname);
size_t fload(FILE *fp, char **wordlist_p);
int main(int argc, char *argv[])
{
FILE *fp;
if (argc < 2) {
usage(argv[0]);
return -1;
}
fp = fopen(argv[1], "r");
if (!fp) {
perror(argv[1]); /* display system error, with the filename */
return -1;
}
if(fload(fp, wordlist_p) < 0) {
fprintf(stderr, "Something went wrong\n")
}
fclose(fp); /* close the file */
return 0;
}
int usage(const char *progname)
{
fprintf(stderr, "Usage is:\n\t%s filename\n", progname);
}
size_t fload(FILE *fp, char **wordlist_p)
{
size_t rv = -1; /* return value */
return rv;
}
Now we run into a conceptual problem. How do we allocate memory for wordlist_p? I mean we don't have any idea about how big the file is, we also don't know how big the biggest word in the file is.
Crude approach
Let's first try an think about it the simple way:
Point to the beginning of the `wordlist_p` with a `tail_pointer`
Read the file line by line, (we assume no hyphenation)
For each line split the line up along white spaces,
Allocate space for the number of words in the `wordlist_p` array
For each word in the split line
Allocate space for the word itself
Save the pointer to the word at the tail_pointer
Advance wordlist_p tail_pointer
Next word
Next Line
Let's look at what the fload function would look like with these steps above,
More to come ##
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.
I came up with this code with the help of #GWW and now I can't free a char**.
Here's my code (it just reads an input file and prints the names in it on the screen):
/* deallocate2D
corresponding function to dynamically deallocate 2-dimensional array using
* malloc.
* accepts a char** as the "array" to be allocated, and the number of rows.
* as with all dynamic memory allocation, failure to free malloc'ed memory
* will result in memory leaks
*/
void deallocate2D(char** array, int nrows) {
/* deallocate each row */
int i;
for (i = 0; i < nrows; i++) {
free(array[i]);
}
/* deallocate array of pointers */
free(array);
}
int readInputFile(FILE *fp, char **file_images) {
num_lines = 0;
int s = 10;
char line[MAX_LENGTH];
char **final_filenames;
while (fgets(line, sizeof line, fp) != NULL) /* read a line */ {
if (line[0] != '\n') {
if (num_lines >= s) {
s += 100;
if ((file_images = (char**) realloc(file_images, s * sizeof (char*))) == NULL) {
printf("Error reallocating space for 2d array: %s\n", strerror(errno));
return -1;
}
}
if ((file_images[num_lines] = malloc(MAX_LENGTH * sizeof (char))) == NULL) {
printf("Error allocating space for 2d array: %s\n", strerror(errno));
return -1;
}
strncpy(file_images[num_lines], line, MAX_LENGTH);
if (file_images[num_lines] == NULL) {
printf("Strncpy failed: %s\n", strerror(errno));
return -1;
}
printf("name of file %d is: %s \n", num_lines, file_images[num_lines]);
num_lines++;
}
}
printf("Num_lines: %d\n",num_lines);
//realloc to number of lines in the file, to avoid wasting memory
if ((final_filenames = realloc(file_images, num_lines * sizeof (char*))) == NULL) {
printf("Error reallocating space for 2d array: %s\n", strerror(errno));
return -1;
} else {
file_images = final_filenames;
deallocate2D(final_filenames, num_lines);
}
return 0;
//don't forget to free lines 2d array! (here or at the end of the code)
}
int main(int argc, char *argv[]) {
//pixel* image;
char **images_filenames;
//check parameters
if (argc < 4) {
printf("Incorrect usage.\nPlease use \"./invert input_filename.ppm charWidth charHeight \"\n");
return -1;
}
printf("Opening input file [%s]\n", argv[1]);
FILE *fpin = fopen(argv[1], "r");
if (fpin == NULL) {
printf("Could not open input file\n");
return -1;
}
if ((images_filenames = ((char**) malloc(10 * sizeof (char*)))) == NULL) {
printf("Error allocating initial space for 2d array: %s\n", strerror(errno));
return -1;
}
if (readInputFile(fpin, images_filenames) == -1) {
printf("Error reading image filenames from input\n");
return -1;
}
fclose(fpin);
printf("###########\n");
deallocate2D(images_filenames, num_lines);
printf("Done!\n");
return 0;
}
So, I don't understand why can't I free final_filenames and images_filenames.
The error that this code gives me is:
*** glibc detected *** ./main: double free or corruption (!prev): 0x0986d228 ***
How do I correctly free my arrays without errors?
The problems are that you are freeing a pointer that may already have been freed, and you don't know how much space is in use don't have a pointer to the most recently allocated space (in general) so you can't free the memory accurately. In main(), you have:
char **images_filenames;
[...]
if ((images_filenames = ((char**) malloc(10 * sizeof (char*)))) == NULL) {
[...]
if (readInputFile(fpin, images_filenames) == -1) {
[...]
deallocate2D(images_filenames, num_lines);
You allocate 10 character pointers, and then pass that array to the readInputFile() function. Inside that function, there is code to reallocate the array, but you have not provided a way for the main program to know what that new address is. The way you do that is either by passing a pointer to whatever it is you want modified, or you have the function return the modified value (or you resort to sordid practices like using global variables instead of parameters - but you shouldn't do that).
So, you need:
if (readInputFile(fpin, &images_filenames) == -1) {
And in the readInputFile() function, you need a whole lot of changes - the big one to deal with the triple-pointer argument, and then a variety of coding problems:
int readInputFile(FILE *fp, char ***ppp_files)
{
num_lines = 0;
int s = 10;
char line[MAX_LENGTH];
char **file_images = *ppp_files;
char **final_filenames;
Update: I didn't notice that this was simply initializing num_lines, not declaring it. Thus, num_lines must be a global variable of some sort...some of the commentary below needs adjusting to allow for this.
So far, the change is (almost) trivial; we're getting a pointer to a 'char **', hence the triple pointer argument. To simplify the following code, make a local copy of the parameter under the old name (file_images) and initialized it with the value that the argument points at. Following code can then continue to work with file_images; just ensure that the argument is updated before returning.
Except...
You assume that 's = 10', but really, you should have the main function tell you how many rows were available. It did allocate 10 rows, but it is not clear without careful scrutiny that was the case. You should have the main() program say how many rows were preallocated - an extra argument to the function. You also face the problem that the main() program cannot tell the deallocate2D() function how many rows are in the array because it does not know. It is not clear how your code compiles; you have a local variable num_lines here, but there's a reference to a variable num_lines in main() for which there is no declaration. The local variable masks any global variable.
while (fgets(line, sizeof line, fp) != NULL) {
if (line[0] != '\n') {
if (num_lines >= s) {
s += 100;
Adding a large number of rows is a good idea; it 'amortizes' the cost of the reallocations.
if ((file_images = (char**) realloc(file_images, s * sizeof (char*))) == NULL)
There are specific problems with the technique you've used though.
Pure code style: when a line contains an if with an embedded assignment and it gets too long, split the assignment out before the condition:
file_images = (char**) realloc(file_images, s * sizeof (char*));
if (file_images == NULL)
Now there's just a subtle bug left. What happens if realloc() fails...
That's right, you've leaked the memory because the value in file_images is null, so there is no way to release what it used to point to. Never write:
x = realloc(x, size);
It leaks memory on failure! Hence, you need:
char **new_space = realloc(file_images, s * sizeof (char*));
if (new_space == NULL)
{
printf("Error reallocating space for 2d array: %s\n",
strerror(errno));
*ppp_files = file_images;
return -1;
}
}
As a general rule, error messages should be printed on stderr; I haven't fixed this.
Note that I carefully copied back the last (non-null) value of file_images into the variable in the main program. It might be appropriate to do the same with the size, too (another interface change), or use a structure to encapsulate the array - size and pointer to its base.
if ((file_images[num_lines] = malloc(MAX_LENGTH * sizeof (char))) == NULL)
{
printf("Error allocating space for 2d array: %s\n", strerror(errno));
return -1;
}
This error return needs to set *ppp_files = file_images;.
strncpy(file_images[num_lines], line, MAX_LENGTH);
if (file_images[num_lines] == NULL) {
printf("Strncpy failed: %s\n", strerror(errno));
return -1;
}
This test is odd; you know that file_images[num_lines] is not null, and strncpy() doesn't change that. You don't need the test and error handling.
printf("name of file %d is: %s \n", num_lines, file_images[num_lines]);
num_lines++;
}
}
printf("Num_lines: %d\n",num_lines);
OK...
//realloc to number of lines in the file, to avoid wasting memory
Nice touch. It is barely worth it; even on a 64-bit machine, you are wasting less than 1 KiB at most. However, no harm in being tidy - good.
if ((final_filenames = realloc(file_images, num_lines * sizeof (char*))) == NULL) {
printf("Error reallocating space for 2d array: %s\n", strerror(errno));
return -1;
Again, you need to set *ppp_files = file_images; before return.
} else {
file_images = final_filenames;
This does not affect the value in the main() program. It would need to be *ppp_files = file_images; again.
deallocate2D(final_filenames, num_lines);
Hold on - you deallocate all your carefully allocated space? So you aren't going to use it after all? The assignment above merely copied a pointer value around; it did not make a copy of the memory...
}
return 0;
//don't forget to free lines 2d array! (here or at the end of the code)
}
This comment is wrong - on successful return, the memory is already deallocated.
Lemme guess - you don't use 'vim' or another 'vi' derivative for editing. People who do have the opening brace of their functions in column 1, because then you can jump forwards or backwards through the file to the start of the next or previous function using ']]' or '[['. It is irksome working with code where that does not work.
Well, that's a starting diagnosis... Here's working code using a structure to relay the array of file names around. I've left the body of the readInputFile() function using local variables that are copied out of the structure, and ensured that the structure is properly updated at all times.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
enum { MAX_LENGTH = 512 };
typedef struct FileNameArray
{
size_t nfiles; /* Number of file names allocated and in use */
size_t maxfiles; /* Number of entries allocated in array */
char **files; /* Array of file names */
} FileNameArray;
static void deallocate2D(FileNameArray *names)
{
for (size_t i = 0; i < names->nfiles; i++)
free(names->files[i]);
free(names->files);
names->nfiles = 0;
names->files = 0;
names->maxfiles = 0;
}
static int readInputFile(FILE *fp, FileNameArray *names)
{
int num_lines = names->nfiles;
int max_lines = names->maxfiles;
char **file_names = names->files;
char line[MAX_LENGTH];
char **final_filenames;
while (fgets(line, sizeof line, fp) != NULL)
{
if (line[0] != '\n')
{
/* Remove newline from end of file name */
char *nl = strchr(line, '\n');
if (nl != 0)
*nl = '\0';
if (num_lines >= max_lines)
{
max_lines += 100;
char **space = realloc(file_names, max_lines * sizeof (char*));
if (space == NULL)
{
fprintf(stderr, "Error reallocating space for 2d array: %s\n",
strerror(errno));
return -1;
}
names->maxfiles = max_lines;
names->files = space;
file_names = space;
}
if ((file_names[num_lines] = malloc(strlen(line) + 1)) == NULL)
{
fprintf(stderr, "Error allocating space for 2d array: %s\n",
strerror(errno));
return -1;
}
names->nfiles++;
strcpy(file_names[num_lines], line);
printf("name of file %d is: %s \n", num_lines, file_names[num_lines]);
num_lines++;
}
}
printf("Num_lines: %d\n", num_lines);
//realloc to number of lines in the file, to avoid wasting memory
if ((final_filenames = realloc(file_names, num_lines * sizeof (char*))) == NULL)
{
fprintf(stderr, "Error reallocating space for 2d array: %s\n",
strerror(errno));
return -1;
}
names->maxfiles = num_lines;
names->files = final_filenames;
return 0;
}
int main(int argc, char *argv[])
{
FileNameArray names = { 0, 0, 0 };
//check parameters
if (argc < 4)
{
fprintf(stderr, "Usage: %s input_filename.ppm charWidth charHeight\n",
argv[0]);
return -1;
}
printf("Opening input file [%s]\n", argv[1]);
FILE *fpin = fopen(argv[1], "r");
if (fpin == NULL) {
fprintf(stderr, "Could not open input file %s (%s)\n",
argv[1], strerror(errno));
return -1;
}
if ((names.files = malloc(10 * sizeof (char*))) == NULL)
{
fprintf(stderr, "Error allocating initial space for 2d array: %s\n",
strerror(errno));
return -1;
}
names.maxfiles = 10;
if (readInputFile(fpin, &names) == -1)
{
fprintf(stderr, "Error reading image filenames from input\n");
return -1;
}
fclose(fpin);
printf("###########\n");
deallocate2D(&names);
printf("Done!\n");
return 0;
}