Sorting string from file - c

I want to sort strings from file; this code compiles well, but it stops working in line 29, when I do words_array[i] = strdup(line);.
From debugger I have "program received signal sigsegv segmentation fault"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int comparator ( const void * elem1, const void * elem2 )
{
return strcmp( *(const char**) elem1, *(const char**) elem2);
}
int main()
{
char filename[]="dane.txt";
FILE* fp;
char* line = NULL;
size_t len = 0;
char** words_array = NULL;
int i = 0,j; // number of elements
// read list from file
if( ( fp = fopen(filename, "r") ) == NULL ) {
fprintf(stderr, "Cannot open source file %s!\n", filename);
exit(1);
}
for(; fgets(line, len, fp) != NULL; ++i) {
// put word in array
words_array = realloc(words_array, sizeof(char*) * (i + 1) );
words_array[i] = strdup(line);
}
fclose(fp);
free(line);
// sort it
qsort(words_array, i, sizeof(char*), comparator);
if( ( fp = fopen(filename, "a+") ) == NULL ) {
fprintf(stderr, "Cannot open source file %s!\n", filename);
exit(1);
}
// write to file and free dynamically allocated memory
for(j = 0; j < i; ++j) {
fprintf(fp, "%s", words_array[j]);
free(words_array[j]);
}
free(words_array);
fclose(fp);
return 0;
}

You never allocated space for line to point to.

Related

How to edit .csv files in C

I'm new at programming, and I need help in my C project. I have to search for a city, confirm it exists in the first file (city.csv), and take its id from there. Then I have to match that id with the corresponding one in the second file (meteo.csv), and then edit its weather information, that is in that second file. However, I don't know how I can take the city id from the first file, and then how to edit the second file after obtaining all the new weather informations. Here is the code:
void addInfo() {
FILE * fp;
char id_city[100];
char city[100];
char humidity[100];
char temp_max[100];
char temp_min[100];
char pressure[100];
char date[100];
printf("Name of the city: ");
scanf("%s", city);
// I think it's here that I have to write the code for take the city's id from the first file
if (id_city != NULL) {
printf("Maximun temperature: ");
scanf("%s", temp_max);
printf("Minimun temperature: ");
scanf("%s", temp_min);
printf("Humidity: ");
scanf("%s", humidity);
printf("Pressure: ");
scanf("%s", pressure);
printf("Date, in the format YYYY-MM-DD: ");
scanf("%s", date);
fp = fopen ("meteo.csv", "a");
fprintf(fp, "%s, %s, %s, %s, %s \n", temp_max, temp_min, humidity, pressure, date); //I think there's something wrong here too...
fclose(fp);
printf("Information edited successfully");
}
The file city.csv has 152 lines and 4 columns:
(id_city,city,county,district)
such as
(56,Lisbon,Lisbon,Lisbon)
The file meteo.csv has 152 lines and 7 columns:
(id_meteo_city,id_city,temp_max,temp_min,humidity,pressure,date)
such as
(56,56,14,5,62,1025,2018-02-12)
The first thing I would do is encapsulate the data in a struct, that makes it
easier to map a line of a CSV file into an object representing a line.
If both files city.csv and meteo.csv have different columns, I'd create a
different struct for each file. If both files have the same columns, you could
use the struct. I assume that both files are different and that city has the
format meteo_id,city_id,name.
typedef struct city_t {
int meteo_id;
int city_id;
char name[100]; // no city should have
// longer than 100 chars
} city_t;
typedef struct meteo_t {
int meteo_id;
int city_id;
int tempt_max;
int tempt_mix;
double humidity;
double preassure;
char date[11];
} meteo_t;
Let's assume that both files are well formatted, otherwise you would have to
write code that checks for errors and handles them, that would be the next step
in the exercise, so I'm going to write only the basic version with basic error
recognition.
#include <stdio.h>
#include <string.h>
#include <errno.h>
// takes 2 params, the filename and a pointer
// to size_t where the number of cities is stored
city_t *read_cities(const char *filename, size_t *len)
{
if(filename == NULL || len == NULL)
return NULL;
FILE *fp = fopen(filename, "r");
if(fp == NULL)
{
fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno));
return NULL;
}
city_t *arr = NULL, *tmp;
*len = 0;
// assuming that no line will be longer than 1023 chars long
char line[1024];
while(fgets(line, sizeof line, fp))
{
tmp = realloc(arr, (*len + 1) * sizeof *arr);
if(tmp == NULL)
{
fprintf(stderr, "could not parse the whole file %s\n", filename);
// returning all parsed cities so far
if(*len == 0)
{
free(arr);
arr = NULL;
}
return arr;
}
arr = tmp;
// %99[^\n] is to read up to 99 characters until the end of the line
if(sscanf(line, "%d,%d,%99[^\n]", &(arr[*len].meteo_id),
&(arr[*len].city_id), arr[*len].name) != 3)
{
fprintf(stderr, "Invalid line format (skipping line):\n%s\n", line);
// skip this line, and decrement *len
(*len)--;
continue;
}
// incrementing only when parsing of line was OK
(*len)++;
}
fclose(fp);
// file is empty or
// all lines have wrong format
if(*len == 0)
{
free(arr);
arr = NULL;
}
return arr;
}
void print_cities(city_t *cities, size_t len, FILE *fp)
{
if(cities == NULL || fp == NULL)
return;
for(size_t i = 0; i < len; ++i)
fprintf(fp, "%d,%d,%s\n", cities[i].meteo_id, cities[i].citiy_id,
cities[i].name);
}
Now I've written the read and write functions for the file citiy.csv assuming the
format meteo_id;city_id;name. The print_cities allows you to print the CSV
content on the screen (passing stdout as the last argument) or to a file
(passing a FILE object as the last argument).
You can use these functions as templates for reading and writing meteo.csv, the
idea is the same.
You can use these function as follows:
int main(void)
{
size_t cities_len;
city_t *cities = read_cities("city.csv", &cities_len);
// error
if(cities == NULL)
return 1;
do_something_with_cities(cities, cities_len);
// update csv
FILE *fp = fopen("city.csv", "w");
if(fp == NULL)
{
fprintf(stderr, "Could not open city.csv for reading: %s\n",
strerror(errno));
free(cities);
return 1;
}
print_cities(cities, cities_len, fp);
fclose(fp);
free(cities);
return 0;
}
Now for your exercise: write a similar function that parses meteo.csv (using
my function as a template shouldn't be that difficult) and parse both files. Now
that you've got them in memory, it's easy to manipulate the data (insert,
update, delete). Then write the files like I did in the example and that's it.
One last hint: how to search for a city:
// returns the index in the array or -1 on error or when not found
int search_for_city_by_name(city_t *cities, size_t len, const char *name)
{
if(cities == NULL || name == NULL)
return -1;
for(size_t i = 0; i < len; ++i)
if(strcmp(name, cities[i].name) == 0)
return i;
// not found
return -1;
}
Now I have given you almost all parts of the assignment, all you have to do is
stick them together and write the same functions for the meteo.csv file.
To edit one field:
void _ERR(char a) {
if (a == "f") printf("\n\tError File !!\n\n");
if (a == "m") printf("\n\tError Memory !!\n\n");
exit(1); }
char* stmm(const char* src) {
char* dst = malloc(strlen(src) + 1);
if (dst == NULL) return NULL;
strcpy(dst, src);
return dst; }
const char* getfield(char* line, int num) {
const char* tok;
for (tok = strtok(line, ",");
tok && *tok;
tok = strtok(NULL, ",\n"))
{
if (!--num)
return tok;
}
return NULL; }
void edit_file(char* FName, char* NewValue, int row, int col) {
int i, r = 0, c;
char line[1024];
FILE* fr, * fw;
fr = fopen(FName, "r");
fw = fopen(FName, "r+");
if (fr == NULL|| fw == NULL) _ERR("f");
while (fgets(line, 1024, fr))
{
char* tmp = stmm(line);
if (tmp == NULL) _ERR("m");
for (i = 0, c = 1; i < strlen(tmp); i++) {
if (tmp[i] == 44) c++;
}
for (i = 0; i < c; i++) {
if (r == row && i+1 == col) {
fprintf(fw,"%s", NewValue);
} else {
free(tmp);
tmp = stmm(line);
if (tmp == NULL) _ERR("m");
fprintf(fw,"%s", getfield(tmp, i + 1));
}
(i < c - 1) ? fprintf(fw,",") : fprintf(fw,"\n");
}
free(tmp);
r++;
}
fclose(fr);
fclose(fw); }
edit_file(".\FileName.csv","NewValue",Row,Column);

Read from CSV file in C returns same value everytime

I have a csv file having values
1,A,X
2,B,Y
3,C,Z
I have to read the CSV file line by line and keep it in a Structure array.
The values are going fine each time in the for loop. But at the end when I am printing the Array, only the last value is being printed.
Somebody please tell me where am I doing the logical error?
struct proc
{
char *x;
char *y;
};
void main()
{
fflush(stdin);
fflush(stdout);
const char s[2] = ",";
char *token;
int rows=0,i,tokenVal=0,rowCount=0;
FILE *fpCount = fopen("data.csv","r");
if(fpCount != NULL)
{
char lineCount[20];
while(fgets(lineCount, sizeof lineCount, fpCount))
rows++;
}
struct proc *pi[rows];
for(i=0;i<rows;i++)
pi[i] = (struct proc*) malloc(sizeof(struct proc));
FILE *fp = fopen("data.csv", "r");
if(fp != NULL)
{
char line[20];
while(fgets(line, sizeof line, fp) != NULL)
{
printf("Start rowCount = %d\t",rowCount);
token = strtok(line, s);
while(token!=NULL)
{
if(tokenVal==0)
{
pi[rowCount]->Id =token;
}
if(tokenVal==1)
{
pi[rowCount]->act = token;
}
printf("\n");
tokenVal++;
token = strtok(NULL,s);
}
tokenVal = 0;
printf("end rowCount = %d\t",rowCount);
rowCount++;
}
fclose(fp);
} else {
perror("data.csv");
}
printf("total %d",rowCount);
int k=0;
for(k=0;k<rowCount;k++)
{
printf(" %d = %s----%s",k,pi[k]->Id,pi[k]->act);
}
}
Diagnosis
The fundamental problem you face is that you are saving pointers to the variable line in your structures, but each new line overwrites what was previously in line, so at the end, only data from the last line is present. It is fortuitous that your lines of data are all the same 'shape'; if the fields were of different lengths, you'd have more interesting, but equally erroneous, results.
Consequently, you need to save a copy of each field, not simply a pointer to the field. The simple way to do that is with POSIX function strdup(). If you don't have the function, you can create it:
char *strdup(const char *str)
{
size_t len = strlen(str) + 1;
char *rv = malloc(len);
if (rv != 0)
memmove(rv, str, len); // or memcpy
return rv;
}
Your code doesn't compile; your data structure has elements x and y but your code uses elements Id and act. You use a VLA of pointers to your struct proc, but it would be sensible to allocate an array of the structure, either as a VLA or via malloc() et al. You should check memory allocations — there isn't a way to check VLAs, though (one reason to use dynamic allocation instead). You could rewind the file instead of reopening it. (It's a good idea to use a variable to hold the file name, even if you only open it once; it makes error reporting better. Also, errors should stop the program, in general, though you did use perror() if the reopen operation failed — but not if the open failed.) You don't need two arrays into which to read the lines. It's a good idea to use far longer buffers for input lines. You should free dynamically allocated memory. Also, see What should main() return in C and C++?; the answer is int and not void (unless perhaps you are on Windows).
Here are three variants of your code, with various aspects of the issues outlined above more or less fixed.
VLA of pointers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct proc
{
char *x;
char *y;
};
int main(void)
{
const char datafile[] = "data.csv";
const char csv_delim[] = ",\n";
int rows = 0, rowCount = 0;
FILE *fpCount = fopen(datafile, "r");
if (fpCount == NULL)
{
fprintf(stderr, "Failed to open '%s' for reading\n", datafile);
exit(EXIT_FAILURE);
}
char lineCount[2000];
while (fgets(lineCount, sizeof(lineCount), fpCount))
rows++;
fclose(fpCount);
printf("Read %d rows from '%s'\n", rows, datafile);
struct proc *pi[rows];
for (int i = 0; i < rows; i++)
pi[i] = (struct proc *)malloc(sizeof(struct proc));
FILE *fp = fopen(datafile, "r");
if (fp == NULL)
{
fprintf(stderr, "Failed to reopen '%s' for reading\n", datafile);
exit(EXIT_FAILURE);
}
char line[2000];
while (fgets(line, sizeof(line), fp) != NULL)
{
printf("Start rowCount = %d\t", rowCount);
int tokenVal = 0;
char *token = strtok(line, csv_delim);
while (token != NULL)
{
if (tokenVal == 0)
{
pi[rowCount]->x = strdup(token);
}
else if (tokenVal == 1)
{
pi[rowCount]->y = strdup(token);
}
printf("[%s]", token);
tokenVal++;
token = strtok(NULL, csv_delim);
}
printf("\tend rowCount = %d\n", rowCount);
rowCount++;
}
fclose(fp);
/* Data validation */
printf("total %d\n", rowCount);
for (int k = 0; k < rowCount; k++)
{
printf("%d = [%s]----[%s]\n", k, pi[k]->x, pi[k]->y);
}
/* Release allocated memory */
for (int k = 0; k < rowCount; k++)
{
free(pi[k]->x);
free(pi[k]->y);
free(pi[k]);
}
return 0;
}
VLA of structures
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct proc
{
char *x;
char *y;
};
int main(void)
{
const char datafile[] = "data.csv";
const char csv_delim[] = ",\n";
int rows = 0, rowCount = 0;
FILE *fpCount = fopen(datafile, "r");
if (fpCount == NULL)
{
fprintf(stderr, "Failed to open '%s' for reading\n", datafile);
exit(EXIT_FAILURE);
}
char lineCount[2000];
while (fgets(lineCount, sizeof(lineCount), fpCount))
rows++;
fclose(fpCount);
printf("Read %d rows from '%s'\n", rows, datafile);
struct proc pi[rows];
FILE *fp = fopen(datafile, "r");
if (fp == NULL)
{
fprintf(stderr, "Failed to reopen '%s' for reading\n", datafile);
exit(EXIT_FAILURE);
}
char line[2000];
while (fgets(line, sizeof(line), fp) != NULL)
{
printf("Start rowCount = %d\t", rowCount);
int tokenVal = 0;
char *token = strtok(line, csv_delim);
while (token != NULL)
{
if (tokenVal == 0)
{
pi[rowCount].x = strdup(token);
}
else if (tokenVal == 1)
{
pi[rowCount].y = strdup(token);
}
printf("[%s]", token);
tokenVal++;
token = strtok(NULL, csv_delim);
}
printf("\tend rowCount = %d\n", rowCount);
rowCount++;
}
fclose(fp);
/* Data validation */
printf("total %d\n", rowCount);
for (int k = 0; k < rowCount; k++)
{
printf("%d = [%s]----[%s]\n", k, pi[k].x, pi[k].y);
}
/* Release allocated memory */
for (int k = 0; k < rowCount; k++)
{
free(pi[k].x);
free(pi[k].y);
}
return 0;
}
Dynamic array of structures
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct proc
{
char *x;
char *y;
};
int main(void)
{
const char datafile[] = "data.csv";
const char csv_delim[] = ",\n";
int num_rows = 0, rowCount = 0;
FILE *fp = fopen(datafile, "r");
if (fp == NULL)
{
fprintf(stderr, "Failed to open '%s' for reading\n", datafile);
exit(EXIT_FAILURE);
}
char line[2000];
while (fgets(line, sizeof(line), fp))
num_rows++;
rewind(fp);
printf("Read %d rows from '%s'\n", num_rows, datafile);
struct proc *pi = calloc(num_rows, sizeof(*pi));
if (pi == 0)
{
fprintf(stderr, "Failed to allocate %zu bytes of memory\n", num_rows * sizeof(*pi));
exit(EXIT_FAILURE);
}
while (fgets(line, sizeof(line), fp) != NULL)
{
printf("Start rowCount = %d\t", rowCount);
int tokenVal = 0;
char *token = strtok(line, csv_delim);
while (token != NULL)
{
if (tokenVal == 0)
{
pi[rowCount].x = strdup(token);
// null check
}
else if (tokenVal == 1)
{
pi[rowCount].y = strdup(token);
// null check
}
printf("[%s]", token);
tokenVal++;
token = strtok(NULL, csv_delim);
}
printf("\tend rowCount = %d\n", rowCount);
rowCount++;
}
fclose(fp);
/* Data validation */
printf("total %d\n", rowCount);
for (int k = 0; k < rowCount; k++)
{
printf("%d = [%s]----[%s]\n", k, pi[k].x, pi[k].y);
}
/* Release allocated memory */
for (int k = 0; k < rowCount; k++)
{
free(pi[k].x);
free(pi[k].y);
}
free(pi);
return 0;
}
Given a data file:
1,A,X
2,B,Y
3,C,Z
3192-2146-9913,Abelone,Zoophyte
all three programs produce the same output:
Read 4 rows from 'data.csv'
Start rowCount = 0 [1][A][X] end rowCount = 0
Start rowCount = 1 [2][B][Y] end rowCount = 1
Start rowCount = 2 [3][C][Z] end rowCount = 2
Start rowCount = 3 [3192-2146-9913][Abelone][Zoophyte] end rowCount = 3
total 4
0 = [1]----[A]
1 = [2]----[B]
2 = [3]----[C]
3 = [3192-2146-9913]----[Abelone]
In the printf(" %d = %s----%s----%s",k,pi[k]->Id,pi[k]->act);
There are four data
%d
%s
%s
%s
but you set only three
k
pi[k]->Id
pi[k]->act

C pass by reference "returns" incorrect content

I want to read some values from a file using a function and pass them to main.
The file has specific format:
string double char
For example 2 lines:
blahblah 0.12 G
testtesttest 0.33 E
I have the following program. Although values are printed correctly in the function, in main only a few of them are printed. The rest are 0.00000 and no character is printed as well. What am I doing wrong?
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int read_file(const char *filename, double **prob, char **sense);
int main(){
double *iprob;
char *sense;
read_file("test.txt", &iprob, &sense);
printf("Main: %lf %c\n", iprob[0], sense[0]);
return 0;
}
int read_file(const char *filename, double **prob, char **sense){
FILE *fp;
char line[100], temp[80];
int i = 0;
fp = fopen(filename, "r");
if (fp == NULL){
fprintf(stderr,"File %s not found!\n", filename);
return 0;
}
//*prob = (double *)malloc(sizeof(double) * 100);
//*sense = (char *)malloc(sizeof(char) * 100);
while( fgets(line, 100, fp) != NULL){
prob[i] = (double *)malloc(sizeof(double));
sense[i] = (char *)malloc(sizeof(char));
if ( sscanf(line, "%s %lf %c", temp, prob[i], sense[i]) < 3 ){
fprintf(stderr, "Parsing error detected at line %d!", i);
fclose(fp);
return 0;
}
else{
printf("%lf %c\n", *prob[i], *sense[i]);
}
i++;
}
fclose(fp);
return 1;
}
You use a double pointer to double in your functions, because you want to update the pointer passed in from main.
The problem is, that the allocated array is in *prob, and therefore you have to address the elements of that array as (*prob)[i].
*prob[i] is the same as *(prob[i]). prob is the pointer to a pointer; it has only one element, so to speak, so any index except 0 is invalid here.
Below is a correction of your code:
It reads in as many entries as there are in the file by reallocating memory as needed.
It returns -1 on failure and the number of items when successful, so you know how many items you can safely address.
You should free both pointers after use.
So:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int read_file(const char *filename, double **prob, char **sense);
int main(){
double *iprob = NULL;
char *sense = NULL;
int i, n;
n = read_file("test.txt", &iprob, &sense);
for (i = 0; i < n; i++) {
printf("Main: %lf %c\n", iprob[i], sense[i]);
}
free(iprob);
free(sense);
return 0;
}
int read_file(const char *filename, double **prob, char **sense){
FILE *fp;
char line[100];
int size = 0;
int i = 0;
*prob = NULL;
*sense = NULL;
fp = fopen(filename, "r");
if (fp == NULL) return -1;
while (fgets(line, sizeof(line), fp) != NULL) {
char temp[80];
if (i >= size) {
size += 8;
*prob = realloc(*prob, size * sizeof(**prob));
*sense = realloc(*sense, size * sizeof(**sense));
if (*prob == NULL || *sense == NULL) {
fclose(fp);
return -1;
}
}
if (sscanf(line, "%79s %lf %c", temp, &(*prob)[i], &(*sense)[i]) < 3) {
fprintf(stderr, "Parsing error detected at line %d!", i);
fclose(fp);
return -1;
}
printf("%lf %c\n", (*prob)[i], (*sense)[i]);
i++;
}
fclose(fp);
return i;
}
change to
*prob = (double *)malloc(sizeof(double) * 100);
*sense = (char *)malloc(sizeof(char) * 100);
while( fgets(line, 100, fp) != NULL){
//prob[i] = (double *)malloc(sizeof(double));
//sense[i] = (char *)malloc(sizeof(char));
if ( sscanf(line, "%s %lf %c", temp, &(*prob)[i], &(*sense)[i]) < 3 ){
fprintf(stderr, "Parsing error detected at line %d!", i);
fclose(fp);
return 0;
}
else{
printf("%lf %c\n", (*prob)[i], (*sense)[i]);
}
i++;
}

Trying to scan a list of names into a double pointer

I have no idea what is wrong here. Trying to scan a list of names:
bob
tim
tony
alison
jim
etc. into double pointer **strings but I keep getting a seg fault dont see where.
void insert_data(char **strings, const char *filename, int size)
{
int j = 0;
FILE* file = fopen(filename, "r");
if(file == NULL)
{
printf("File could not be opened");
return;
}
for(j=0; j<size; j++)
{
fscanf(file,"%s", strings[j]);
printf("%s\n", strings[j]);
}
fclose(file);
}
I have a separate function to allocate memory but it still seg faults
void allocate(char ***strings, int size)
{
strings = malloc(size * sizeof(char*));
if(strings == NULL)
{
printf("Could not allocate memory\n");
}
int i;
for(i=0;i<size;i++)
{
*(strings+i) = malloc(MAX_STRING_LEN * sizeof(char));
if(strings == NULL)
{
printf("Could not allocate memory\n");
}
}
}
The core of your function is more or less sound; the problem is more likely in the way you invoke it than in the function itself. The following code, which has only minor fixes to the code in the function, works OK.
#include <stdio.h>
static int insert_data(char **strings, const char *filename, int size)
{
int j = 0;
FILE *file = fopen(filename, "r");
if (file == NULL)
{
fprintf(stderr, "File %s could not be opened\n", filename);
return 0;
}
for (j = 0; j < size; j++)
{
if (fscanf(file, "%s", strings[j]) != 1)
return j;
printf("%s\n", strings[j]);
}
fclose(file);
return size;
}
int main(int argc, char **argv)
{
char data[10][20];
char *str[10];
if (argc != 2)
{
fprintf(stderr, "Usage: %s file\n", argv[0]);
return 1;
}
for (int i = 0; i < 10; i++)
str[i] = data[i];
int n = insert_data(str, argv[1], 10);
for (int i = 0; i < n; i++)
printf("%d: [%s] [%s]\n", i, str[i], data[i]);
// Invalid - incompatible pointer type!
// int m = insert_data(data, "data2", 10);
return 0;
}
Note that given the function prototype, you must pass an array of pointers, rather than trying to pass a pointer to a 2D array of characters. The compiler warns if you try to misuse the function.
change to
#define _S(x) #x
#define S(x) _S(x)
void insert_data(char **strings, const char *filename, int size)
{
int j = 0;
FILE* file = fopen(filename, "r");
if(file == NULL)
{
printf("File could not be opened");
return;
}
for(j=0; j<size; j++)
{
fscanf(file, "%" S(MAX_STRING_LEN) "s", strings[j]);//E.G. #define MAX_STRING_len 64
printf("%s\n", strings[j]);
}
fclose(file);
}
void allocate(char ***strings, int size)
{
*strings = malloc(size * sizeof(char*));
if(*strings == NULL)
{
printf("Could not allocate memory\n");
return ;
}
int i;
for(i=0;i<size;i++)
{
(*strings)[i] = malloc((MAX_STRING_LEN+1) * sizeof(char));
if((*strings)[i] == NULL)
{
printf("Could not allocate memory\n");
return ;
}
}
}

Unexplainable change in C variable

I've written a simple C program to convert char into Tokens. Things work fine but I'm unable to understand why the size variable value is changing.
typedef struct _token {
int val;
} Token;
void parse( char* code, int size, Token** tokens ) {
int i = 0;
for (; i < size; i++) {
tokens[i] = malloc(sizeof(Token));
tokens[i]->val = code[i];
}
}
int execute( char *path ) {
char* code;
if ( read_file( path, &code ) != 0 ) {
return -1;
}
int size = strlen(code) - 1;
printf("BEFORE PARSE: %d\n", size); // 1st printf
Token *tokens;
parse( code, size, &tokens );
printf("AFTER PARSE: %d\n", size); // 2nd printf
return 0;
}
if code contains "abcde", the output is:
BEFORE PARSE: 5
AFTER PARSE: 142786584
The second printf displays different values on different runs.
Please help !
PS: I'm a C noob !
EDIT:
int read_file(char* path, char** code) {
FILE* fp = fopen ( path , "rb" );
if( !fp ) {
return -1;
}
fseek( fp , 0L , SEEK_END);
long lSize = ftell( fp );
rewind( fp );
/* allocate memory for entire content */
*code = calloc( 1, lSize+1 );
if( !*code ) {
fclose( fp );
return -1;
}
/* copy the file into the buffer */
if( 1 != fread( *code , lSize, 1 , fp) ) {
fclose(fp);
return -1;
}
fclose( fp );
return 0;
}
You have a typical case of buffer overflow.
char* code;
Allocates a pointer to character (typically 8 bytes), not a buffer to hold your file data.
Same with
Token *tokens;
When you write to tokens in parse you overwrite part of your stack and size with it.
Allocate enough memory for them!
char * code = malloc(0x1000);
Token *tokens = malloc(0x100 * sizeof(Token *));
And pass the pointer, not it's address:
read_file( path, code );
parse( code, size, tokens );
Here is corrected code:
typedef struct _token {
int val;
} Token;
void parse( char* code, int size, Token* tokens ) {
int i = 0;
for (; i < size; i++) {
// you already have memory now
tokens[i]->val = code[i];
}
}
int execute( char *path ) {
char* code = malloc(0x1000);
if ( read_file( path, code ) != 0 ) {
return -1;
}
int size = strlen(code) - 1;
printf("BEFORE PARSE: %d\n", size); // 1st printf
Token *tokens = calloc(sizeof(Token), 0x100);
parse( code, size, tokens );
printf("AFTER PARSE: %d\n", size); // 2nd printf
return 0;
}
It is because tokens is never initialized. Change it to:
Tokens **tokens = malloc(sizeof(Tokens *) * size);
Don't forget to free the memory when you are done with it:
for (; i < size; i++) {
free(tokens[i]);
}
free(tokens);

Resources