Difficulty setting pointers in dynamically allocated nested structs - c

Been at this thing for awhile. I'm writing a C program that parses a csv file into a nested struct. When I pass the ** to the struct into the add_field function, I am able to get all of the pointers until I get to the field[(* f) - 1] pointer of the field array. That always returns NULL and I cannot figure out why. Any help would be greatly appreciated. Thank you.
Sorry for the huge code dump but I didn't want to leave anything out. I've been checking for null after every alloc but haven't incorporated them yet in this rewrite (I’m watching variable values with step in on clion).
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
/* Inline function for handling errors during memory allocation */
extern int errno;
#ifndef error
#define ERROR static inline void *
__attribute((always_inline)) ERROR error() {
printf("%d\n", errno);
printf("%s", strerror(errno));
exit(EXIT_FAILURE);
}
#endif
/* ------------------------------------------------------------ */
#define MAX_BUFFER_SIZE 100
typedef struct data_field {
char * data;
size_t fldsize;
} FIELD;
typedef struct data_set {
int field_count;
size_t setsize;
FIELD ** field;
} SET;
typedef struct csv_file {
char * filename;
int set_count;
size_t filesize;
SET ** set;
} CSV;
CSV * alloc_csv();
void add_set(CSV ** fp, const int * setcnt);
void add_field(SET **sp, char *buffer, const int *f);
char * file_read(char * file_name);
int main()
{
int b = 0;
ulong bufcnt = 0;
int fldcnt = 0;
int setcnt = 0;
int bmax = 100;
char tok1 = '\n';
char tok2 = ',';
char * buffer;
char * stream;
stream = file_read("/home/jonathon/Documents/programming/personal/csv/data_files/MOCK_DATA.csv");
CSV * file = {0};
void * filetmp = malloc(sizeof(CSV));
file = filetmp;
file->set_count = 0;
void * arrtmp = calloc(1, sizeof(SET *));
file->set = (SET **)arrtmp;
void * settmp = calloc(1, sizeof(SET));
file->set[0] = (SET *)settmp;
setcnt++;
void * buftmp = malloc(sizeof(char) * MAX_BUFFER_SIZE);
buffer = buftmp;
// read stream until end of field
buftmp = malloc(sizeof(char) * MAX_BUFFER_SIZE);
buffer = buftmp;
for (int c = 0; stream[c] != '\0'; c++)
{
if (b >= bmax)
{
buftmp = realloc(buffer, sizeof(char) * (MAX_BUFFER_SIZE + bmax));
buffer = buftmp;
}
switch (stream[c])
{
case 10:
buffer[b] == '\0';
b = 0;
break;
case 44:
buffer[b] == '\0';
add_field(&file->set[setcnt - 1], buffer, &fldcnt);
fldcnt++;
b = 0;
break;
default:
buffer[b] = stream[c];
b++;
}
}
}
void add_field(SET ** sp, char * buffer, const int * f)
{
ulong buflen = strlen(buffer + 1);
if ((*f) == 0)
{
(*sp)->field = (FIELD **)calloc(1, sizeof(FIELD *));
}
else
{
(*sp)->field = (FIELD **)realloc((*sp)->field, sizeof(FIELD *) * ((*f) + 1));
}
(*sp)->field[(*f) - 1] = (FIELD *)calloc(1, sizeof(FIELD));
(*sp)->field[(*f) - 1]->data = (char *)calloc(buflen, sizeof(char));
memcpy((*sp)->field[(*f) - 1]->data, buffer, buflen * sizeof(char));
}
void free_csv(CSV ** fp, const int * setcnt, const int * fldcnt)
{
for (int i = 0; i < * setcnt; i++)
{
for (int j = 0; j < * fldcnt; j++)
{
free((* fp)->set[i]->field[j]->data);
free((* fp)->set[i]->field[j]);
}
free((* fp)->set[i]->field);
free((* fp)->set[i]);
}
free((* fp)->set);
free(* fp);
}
char *file_read(char* file_name)
{
FILE *fp = fopen(file_name, "rb");
size_t file_size;
char* file_buffer = NULL;
if (!fp)
{
perror("Error: ");
exit(EXIT_FAILURE);
}
else
{
// Seek to end of file to get file file_size
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
file_buffer = (char *)calloc(file_size, sizeof(char)); // Allocate buffer file_size
if (!file_buffer)
{
perror("Error: ");
exit(EXIT_FAILURE);
}
// Seek to beginning of file to read from start.
fseek(fp, 0, SEEK_SET);
if (fread(file_buffer, file_size, 1, fp) != 1) // Read into buffer
{
perror("Error: ");
exit(EXIT_FAILURE);
}
fclose(fp);
}
return file_buffer;
}```

Related

Removing extra line at the end of a file parsed into a double dimensional array of characters (char **) in C

I am currently developing a function in C that parses a file into a double dimensional array of characters (char **), the problem is that I get an extra line at the end, and I don't see how to fix that.
Can you help me?
Ps: My school requires me to use getline() and fopen().
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void my_free_word_array(char **word_array)
{
size_t i = 0;
if (!word_array) {
return;
}
while (word_array[i] != NULL) {
free(word_array[i]);
++i;
}
free(word_array);
}
ssize_t my_put_str_arr(char **arr, int fd)
{
char cr = '\n';
ssize_t count = 0;
size_t i = 0;
if (!arr)
return -1;
while (arr[i]) {
count += write(fd, arr[i], strlen(arr[i]));
count += write(fd, &cr, 1);
i++;
}
return count;
}
void append_word_array(char ***array, char *line)
{
size_t array_len = 0;
while ((*array)[array_len] != NULL) {
array_len++;
}
size_t len = strlen(line);
if (line[len - 1] == '\n') {
line[len - 1] = '\0';
}
(*array)[array_len] = strdup(line);
(*array) = realloc((*array), (array_len + 2) * sizeof(char *));
(*array)[array_len + 1] = NULL;
}
void fill_from_file(char ***array, FILE *file)
{
char *line_buff = NULL;
size_t line_buff_size = 0;
ssize_t line_size = getline(&line_buff, &line_buff_size, file);
while (line_size >= 0) {
append_word_array(array, line_buff);
free(line_buff);
line_buff = NULL;
line_size = getline(&line_buff, &line_buff_size, file);
}
free(line_buff);
}
char **my_load_file_to_word_array(const char *filepath)
{
char **word_array = NULL;
FILE *file = fopen(filepath, "r");
if (!file) {
return NULL;
}
word_array = malloc(sizeof(char *));
if (!word_array) {
return NULL;
}
word_array[0] = NULL;
fill_from_file(&word_array, file);
fclose(file);
return word_array;
}
int main (int argc, char **argv)
{
char **file = my_load_file_to_word_array(argv[1]);
my_put_str_arr(file, 1);
my_free_word_array(file);
return 0;
}
Here is the content of the tested file (I added the \n \0 to make it easier for you to see):
My name is Saul.\n
I am Saul Goodman.\n
Better call Saul.\0
And this is the result I get :
My name is Saul.
I am Saul Goodman.
Better call Saul.
The "problem" with your code is that the function my_put_str_arr() prints the stored lines eached followed by a single \n character. If you don't want to print the last \n you would need to test if a next line exists. You could change your loop as follows:
while (arr[i]) {
count += write(fd, arr[i], strlen(arr[i]));
i++;
if (arr[i]) {
count += write(fd, &cr, 1);
}
}

Update array of strings in function

I have a working example of copy lines from a file into an array of strings. I want to move the code to copy the lines into a function to which I simply pass a pointer to the array of strings, where the lines will be stored, and a pointer to the file. However, I have tried to move the code into a function and keep getting seg faults. I have tried debugging using GDB and it seems like the problem is with the memory allocation to rows. But I can't work out what the problem is. realloc seems to be working correctly since I find the size of row increases on the 3rd iteration (using malloc_usable_size(*rows)), but then seg faults. I'm compiling with gcc -Wall -Wextra -pedantic -std=c99 -g c_programs/read_file_function.c on Linux.
Working example
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Please supply a file path:\n%s <file path>\n", argv[0]);
return EXIT_FAILURE;
}
FILE *fp = fopen(argv[1], "r");
if (!fp)
{
perror("ERROR");
return EXIT_FAILURE;
}
char **rows = (char **)malloc(sizeof(char *));
char *lineBuf = NULL;
size_t n = 0;
size_t nLines = 0;
ssize_t lineLength = 0;
size_t i = 0;
while ((lineLength = getline(&lineBuf, &n, fp)) != -1)
{
lineBuf[strcspn(lineBuf, "\n")] = 0;
lineBuf[strcspn(lineBuf, "\r")] = 0;
rows[i] = (char *)malloc(lineLength + 1);
strcpy(rows[i], lineBuf);
i++;
nLines = i;
rows = (char **)realloc(rows, (nLines + 1) * sizeof(char *));
}
printf("nLines: %lu\n", nLines);
printf("row 1: %s\n", rows[0]);
printf("row 2: %s\n", rows[1]);
printf("row 2: %s\n", rows[10]);
return 0;
}
Non working function version
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
size_t readFile(FILE **fp, char ***rows)
{
char *lineBuf = NULL;
size_t n = 0;
size_t nLines = 0;
ssize_t lineLength = 0;
size_t i = 0;
while ((lineLength = getline(&lineBuf, &n, *fp)) != -1)
{
lineBuf[strcspn(lineBuf, "\n")] = 0;
lineBuf[strcspn(lineBuf, "\r")] = 0;
*rows[i] = (char *)malloc(lineLength + 1);
strcpy(*rows[i], lineBuf);
i++;
nLines = i;
*rows = (char **)realloc(*rows, (nLines + 1) * sizeof(char *));
}
return nLines;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Please supply a file path:\n%s <file path>\n", argv[0]);
return EXIT_FAILURE;
}
FILE *fp = fopen(argv[1], "r");
if (!fp)
{
perror("ERROR");
return EXIT_FAILURE;
}
char **rows = (char **)malloc(sizeof(char *));
size_t nLines = readFile(&fp, &rows);
printf("nLines: %lu", nLines);
printf("row 1: %s", rows[0]);
printf("row 2: %s", rows[1]);
return 0;
}
*rows[i] is doing *(rows[i]) - accessing ith element in the array of rows, and then dereferencing it. You want to do (*rows)[i] - dereference rows and then access ith element.
I advise to:
readFile(..., char ***rows0) {
char **rows = NULL; // temporary internal variable
...
// use rows normally
rows = stuff();
...
// when finished, assign once
*rows0 = rows;
return nLines;
}
But do not be a 3-star programmer. At best, use a structure, -> is easy to use. Like:
struct string {
char *str;
};
struct lines {
struct string *strs;
size_t cnt;
};
// #return 0 on success, otherwise error
int readFile(...., struct lines *p) {
// initialization
p->cnt = 0;
p->strs = NULL;
...
void *pnt = realloc(p->strs, (p->cnt + 1) * ....);
if (!pnt) { /* handle error */ return -1; }
p->strs = pnt;
p->strs[p->cnt]->str = malloc(lineLenght + 1);
if (!p->strs[p->cnt]->str) { /* handle error */ return -2; }
strcpy(p->strs[p->cnt]->str, lineBuf);
p->cnt++;
...
return 0; /* success */
}
int main(int argc, char **argv) {
struct lines p = {0};
if (readFile(..., &p)) {
/* handle error */
}
printf("nLines: %zu\n", p.cnt);
Do not pre-allocate memory. Initialize memory with NULL and call realloc before using memory. realloc(NULL is the same as malloc().
Check for allocation errors.

How to loop a nested array in C

I've been developing a guessing game in which the goal is to guess the character selected by the user among specific characters, anyway, my first and only idea is to create an array with the questions to be asked, and each question has its options like in the code below I'm a newbie in C language so that I there are several things which I'm not sure how to handle. In short, I'd like to know how can I loop over the array showing to the user the questions with its questions to be answered? Here's the code.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define ROW 500
#define LINE 200
//Read file and append to an array buffer
char *characters(){
char *source = NULL;
FILE *fp = fopen("file.txt", "r");
if (fp != NULL) {
/* Go to the end of the file. */
if (fseek(fp, 0L, SEEK_END) == 0) {
/* Get the size of the file. */
long bufsize = ftell(fp);
if (bufsize == -1) { /* Error */ }
/* Allocate our buffer to that size. */
source = malloc(sizeof(char) * (bufsize + 1));
/* Go back to the start of the file. */
if (fseek(fp, 0L, SEEK_SET) != 0) { /* Error */ }
/* Read the entire file into memory. */
size_t newLen = fread(source, sizeof(char), bufsize, fp);
if ( ferror( fp ) != 0 ) {
fputs("Error reading file", stderr);
} else {
source[newLen++] = '\0'; /* Just to be safe. */
}
}
fclose(fp);
}
return source;
}
char *strndup(const char *s, size_t n) {
char *p;
size_t n1;
for (n1 = 0; n1 < n && s[n1] != '\0'; n1++)
continue;
p = malloc(n + 1);
if (p != NULL) {
memcpy(p, s, n1);
p[n1] = '\0';
}
return p;
}
// User input
char *input(){
char *value;
char buffer[10];
int j = 0;
while( j < 1 && fgets(buffer, 10, stdin) != NULL){
value = strndup(buffer, 10);
j++;
}
return value;
}
// Main function
int main (void)
{
char *questions[] = {
"Genre",{"male","female"},
"Hair", {"black","red","blond"},
"Cloths",{"dress","shirt","pants"},
"pet", {"dog","cat","pig"}
};
int asked[4] = {0};
char *answers[5];
char buffer[6];
srand(time(NULL));
for (int i = 0; i < 4; i++) {
int q = rand() % 4;
while (asked[q])
q = rand() % 4;
asked[q]++;
printf ("%s\n", questions[q]);
answers[i] = input();
}
for(int i = 0; i < 4; i++)
{
printf(" %s ",answers[i]);
}
return 0;
}
That's the file's structure I'll compare as long as I have all the answers from the user.
female,blond,vestido,pig,character b
male,black,shirt,pants,dog,character c
male,black,shirt,pants,cat,character d
female,blond,dress,cat,character A
male,red,shirt,pants,pig,character e

Copying a file into char array dynamically

I have code for copying a text file (not binary) into an array of chars.
I am trying to copy the contents of a .txt file into a char* array.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
bool is_not_binary(const void *data, size_t len)
{
return memchr(data, '\0', len) != NULL;
}
int main(void)
{
char* file_name="./bash_example.sh";
FILE *file = fopen (file_name, "r");
size_t size = 64;
const size_t line_size = 300;
char *mem = malloc(size);
if (mem == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
*mem = 0;
if (file != NULL && is_not_binary(file_name,line_size)) {
char* line = malloc(line_size);
while (fgets(line, line_size, file) != NULL) {
size_t total = strlen(mem) + strlen(line) + 1;
if (size < total) {
size_t newsize = (total & ~1U) << 1;
char *tmp = realloc(mem, newsize);
if (tmp == NULL) {
perror("realloc");
exit(EXIT_FAILURE);
}
mem = tmp;
size = newsize;
}
strcat(mem, line);
}
}
printf("%s",mem);
return 0;
}
But in my code I must specify a static size:
size_t size = 64;
const size_t line_size = 300;
I want to remove this and I want dynamic allocation, is it possible?
Actually it's static code, 64 and 300.
I coded up an example that makes use of fopen, fread, and the classic example doubling the buffer size as the content grows.
int main()
{
const size_t initial_size = 1024;
char* contents = (char*)malloc(initial_size);
size_t length = 0;
size_t allocated = initial_size;
FILE* file = fopen("./sbatch_example.sh", "r");
if (file)
{
while (1)
{
size_t remaining = allocated - length;
size_t result = 0;
if (remaining == 0)
{
contents = (char*)realloc(contents, allocated*2);
allocated = allocated*2;
remaining = allocated-length;
}
result = fread(contents+length, 1, remaining, file);
length += result;
if (result==0) /* EOF */
{
break;
}
}
}
if (file)
{
fclose(file);
file = NULL;
}
/* at this point, "contents" is your file data bytes
and "length" is the number of bytes copied into that array*/
/*optional: append a null char to the end of the buffer to make it easier for debugging and print statements */
contents = (char*)realloc(contents, length+1);
contents[length] = '\0';
free(contents);
return 0;
}
You can use ftell() to get the total length in advance and fread() to read a whole at one time as below.
FILE *fp = fopen("./sbatch_example.sh", "r");
if (fp) {
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
rewind(fp);
char *mem = malloc(size+1);
size_t nr = fread(mem, size, 1, fp);
}

Reading information from text file in C

I'm new to C; please try to help me as much as you can.
I'm getting as arguments to main() pointers to files,
so in a for loop I fopen() them and want to send them to a function that will
read the text info inside them and put it in char variables.
Here is an example file:
#station name
Station Name : A1
#octan of fuel 6.54 full service price 6.40 self service
Octan95,6.54,6.40
Octan98,8.30,8.15
#carNum,Octan,numOfLiters,Kind of service
22-334-55,95,31.3,FullService
22-334-55,95,31.3,SelfService
11-444-77,95,12,FullService
11-444-77,95,44.1,FullService
11-444-77,95,11.22,SelfService
The text has fields separated with commas, and I need the information between those commas to be added to vars.
What will be the best way or function to read these text files?
Also should I expect '\n' after each line or will it stream as one big char[] without the new line character?
read file line by line
use strtok function to get everything in between commas
read file line by line and use sscanf with return-value to get everything in between commas
Some 200 lines of code later...and using a slightly modified version of your data file (note that the second header line in the original is missing all the commas):
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
** Example data:
**
** #station name
** Station Name : A1
** #octan of fuel,full service price,self service price
** Octan95,6.54,6.40
** Octan98,8.30,8.15
** #carNum,Octan,numOfLiters,Kind of service
** 22-334-55,95,31.3,FullService
** 22-334-55,95,31.3,SelfService
** 11-444-77,95,12,FullService
** 11-444-77,95,44.1,FullService
** 11-444-77,95,11.22,SelfService
**
** - Header lines are followed by one or more data lines
** - Number of fields in header matches number of fields in each data line
** - Commas separate fields and do not appear within fields (not full CSV)
*/
/* A Line structure holds the fields for one line */
typedef struct Line
{
size_t num_fields;
char **fields;
} Line;
/* A Section structure holds the header line and the set of data lines */
typedef struct Section
{
size_t num_rows;
size_t num_cols;
Line header;
Line *lines; /* Array of lines - num_rows entries in array */
} Section;
/* An Info structure holds all the sections for a single file */
typedef struct Info
{
size_t num_sections;
Section *sections;
} Info;
static void err_exit(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(1);
}
static void *xrealloc(void *old_data, size_t nbytes)
{
void *new_data = realloc(old_data, nbytes);
if (new_data == 0)
err_exit("Out of memory!\n");
return new_data;
}
static void *xmalloc(size_t nbytes)
{
void *new_data = malloc(nbytes);
if (new_data == 0)
err_exit("Out of memory!\n");
return new_data;
}
/* Duplicate a string of given length (excluding NUL) */
static char *xstrndup(const char *str, size_t len)
{
char *new_data = xmalloc(len+1);
memmove(new_data, str, len);
new_data[len] = '\0';
return new_data;
}
static void dump_line(FILE *fp, const Line * const line)
{
size_t i;
const char *pad = "";
for (i = 0; i < line->num_fields; i++)
{
fprintf(fp, "%s%*s", pad, 1, line->fields[i]);
pad = " ";
}
fputc('\n', fp);
}
static void dump_section(FILE *fp, const char *tag, const Section * const section)
{
if (tag != 0)
fprintf(fp, "Dump Section: %s\n", tag);
fprintf(fp, "Number of columns: %zd\n", section->num_cols);
fprintf(fp, "Number of lines: %zd\n", section->num_rows);
dump_line(fp, &section->header);
for (size_t i = 0; i < section->num_rows; i++)
dump_line(fp, &section->lines[i]);
}
static void dump_info(FILE *fp, const char *tag, const Info * const info)
{
size_t i;
fprintf(fp, "Dump Information: %s\n", tag);
fprintf(fp, "Number of sections: %zd\n", info->num_sections);
for (i = 0; i < info->num_sections; i++)
{
char title[20];
snprintf(title, sizeof(title), "%d", i+1);
dump_section(fp, title, &info->sections[i]);
}
fprintf(fp, "End of Information Dump\n");
}
static int num_fields(const char *buffer)
{
size_t posn = 0;
size_t next;
int count = 0;
while ((next = strcspn(buffer + posn, ",\n")) > 0)
{
count++;
if (buffer[posn+next] == '\n')
break;
posn += next + 1;
}
return count;
}
static void set_line(Line *line, int nfields, const char *buffer)
{
size_t posn = 0;
line->num_fields = nfields;
line->fields = xmalloc(nfields * sizeof(*line->fields));
for (int i = 0; i < nfields; i++)
{
size_t next = strcspn(buffer+posn, ",\n");
line->fields[i] = xstrndup(buffer+posn, next);
if (buffer[posn+next] == '\n')
{
if (i != nfields - 1)
err_exit("Internal error: field count mismatch\n");
break;
}
posn += next + 1;
}
}
static int add_section(Info *info, char *buffer)
{
int nfields = num_fields(buffer);
int nsections = info->num_sections + 1;
info->sections = xrealloc(info->sections, nsections * sizeof(*info->sections));
info->num_sections = nsections;
Section *new_section = &info->sections[nsections-1];
new_section->num_cols = nfields;
new_section->num_rows = 0;
set_line(&new_section->header, nfields, buffer);
new_section->lines = 0;
return nfields;
}
/* Beware - very compact code! */
static void add_line_to_section(Section *section, const char *buffer, int nfields)
{
section->lines = xrealloc(section->lines, (section->num_rows + 1) * sizeof(*section->lines));
set_line(&section->lines[section->num_rows++], nfields, buffer);
}
static int peek(FILE *fp)
{
int c;
if ((c = getc(fp)) != EOF)
ungetc(c, fp);
return c;
}
static void read_info(FILE *fp, Info *info)
{
char buffer[1024];
while (fgets(buffer, sizeof(buffer), fp) != 0)
{
if (*buffer != '#')
err_exit("Format error: expected line beginning '#' (got '%.*s')\n",
10, buffer);
int nfields = add_section(info, buffer+1);
int c;
Section *cursect = &info->sections[info->num_sections-1];
while ((c = peek(fp)) != EOF && c != '#')
{
if (fgets(buffer, sizeof(buffer), fp) != 0)
{
int lfields = num_fields(buffer);
if (lfields != nfields)
err_exit("Mismatch in number of fields (got %d, wanted %) at '%*s'\n",
lfields, nfields, 20, buffer);
add_line_to_section(cursect, buffer, nfields);
}
}
}
}
int main(int argc, char **argv)
{
int i;
Info info = { 0, 0 };
for (i = 1; i < argc; i++)
{
FILE *fp;
if ((fp = fopen(argv[i], "r")) != 0)
{
read_info(fp, &info);
dump_info(stdout, "After loop", &info);
}
else
fprintf(stderr, "Failed to open file %s (%s)\n", argv[i], strerror(errno));
}
dump_info(stdout, "End of main loop", &info);
return 0;
}
The code is not optimal in most senses - it allocates far too many small bits of memory. I also got lazy and didn't write the code to free the memory. I don't think it would be a good idea to hand this in as your code, though.

Resources