I'm currently trying to compile a crc calculator I took from github and am having difficulty compiling it on visual studio 2015. I get the error expected constant expression for the line:
char paths[strlen(src) + 1 + strlen(name) + 2 + 1];
Thoughts on how I can resolve the error?
static int create_source(char *src, char *name, FILE **head, FILE **code) {
// for error return
*head = NULL;
*code = NULL;
// create the src directory if it does not exist
int ret = _mkdir(src, 0755);
if (ret && errno != EEXIST)
return 1;
// construct the path for the source files, leaving suff pointing to the
// position for the 'h' or 'c'.
char paths[strlen(src) + 1 + strlen(name) + 2 + 1];
char *suff = stpcpy(path, src);
*suff++ = '/';
suff = stpcpy(suff, name);
*suff++ = '.';
suff[1] = 0;
// create header file
*suff = 'h';
*head = fopen(path, "wx");
if (*head == NULL)
return errno == EEXIST ? 2 : 1;
// create code file
*suff = 'c';
*code = fopen(path, "wx");
if (*code == NULL) {
int err = errno;
fclose(*head);
*head = NULL;
*suff = 'h';
unlink(path);
return err == EEXIST ? 2 : 1;
}
// all good -- return handles for header and code
return 0;
}
Your immediate problem is you are attempting to use a VLA (Variable Length Array), introduced into the standard with C99, with a compiler that does not support VLAs. Without VLA support, arrays must be declared with an integer constant (not simply a const int). As of C11, support for VLAs is optional.
To solve your immediate problem and provide portability, simply allocate storage for paths instead with malloc. The free the memory before you return from your function (either though an error return or on success)
You can do something like:
size_t pathlen = strlen(src) + 1 + strlen(name) + 2 + 1;
char *paths = malloc (pathlen); /* allocate storage for paths */
if (!paths) { /* validate EVERY allocation */
perror ("malloc-paths");
return 3; /* or however you want to handle the error */
}
char *suff = stpcpy(path, src);
...
*head = fopen(path, "wx");
if (*head == NULL) {
free (path); /* free paths */
return errno == EEXIST ? 2 : 1;
}
...
if (*code == NULL) {
int err = errno;
free (path); /* free paths */
fclose(*head);
*head = NULL;
*suff = 'h';
unlink(path);
return err == EEXIST ? 2 : 1;
}
free (path); /* free paths */
return 0;
There is a small overhead for the allocation and free, but it is negligible in your case where there is a single allocation and single free.
As a simple alternative to David's solution, you could also use FILENAME_MAX ...
[...] which expands to an integer constant expression that is the size needed for an array of char large enough to hold the longest file name string that the implementation guarantees can be opened; (§7.21.1, ISO C11)
like this
char paths[FILENAME_MAX];
You might like to check that you don't overflow this size, though.
Related
I am trying to implement a linked list data structure that represents a folder tree.
The structures below:
typedef struct SRC_ERROR SRC_ERROR;
struct SRC_ERROR {
int error_code;
char *error;
};
typedef struct SRC_FILE SRC_FILE;
struct SRC_FILE {
char *entry;
char md5[MD5_DIGEST_LENGTH];
};
typedef struct SRC SRC; //Source file tree with md5 entry char for source verification.
struct SRC {
SRC_ERROR error;
char *name;
char *full_path;
SRC_FILE **entries;
SRC *next_dir;
};
The idea was that each directory will be stored in SRC the SRC_FILE is to be used as an array to store the filename and MD5 hash for each file.
The scan_source() below populates the structures.
SRC *scan_source(char *source_path) {
SRC *source = malloc(sizeof(SRC));
source->error.error_code = OK;
int count = 0;
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(source_path))) {
source->error.error_code = ERROR;
source->error.error = "Unable to open source directory.\n";
return source;
}
source->entries = (SRC_FILE **)malloc(sizeof(SRC_FILE *) * count);
if (source->entries == NULL) {
source->error.error_code = ERROR;
source->error.error = "Unable to allocate memory to file entry tree\n";
}
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR) {
char path[PATH_MAX];
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(path, sizeof(path), "%s/%s", source_path, entry->d_name);
printf("[%s] - %s\n", entry->d_name, path);
//add new node
source = add_dir(source, insert_dir_node(entry->d_name, path));
scan_source(path);
} else
if (entry->d_type == DT_REG) {
printf("[FILE] - %s\n", entry->d_name);
source->entries[count]->entry = entry->d_name; //SEGFAULT HERE
count++;
source->entries = realloc(source->entries, sizeof(SRC_FILE *) * (count));
}
}
closedir(dir);
return source;
}
I am having issues with memory management. I am getting intermittent seg faults when the directory is structured in certain ways.
I have marked the line that the debugger has flagged
source->entries[count]->entry = entry->d_name; //SEGFAULT HERE
I thought that I allocated memory for each structure but maybe I have not done this correctly or there is an underlying problem with the data structure entirely?
For Example:
test> tree
.
└── Text
0 directories, 1 file
This causes a seg fault. Whereas, this does not:
/test> tree
.
├── another sample
│ └── Text
└── sample folder
2 directories, 1 file
Additional functions that are used:
SRC *add_dir(SRC *file_tree, SRC *new_dir) {
new_dir->next_dir = file_tree;
return new_dir;
}
SRC *insert_dir_node(char *name, char *full_path) {
SRC *next_dir;
next_dir = (SRC *)emalloc(sizeof(SRC));
next_dir->name = name;
next_dir->full_path = full_path;
next_dir->next_dir = NULL;
return next_dir;
}
I started looking at the code, and the first issue I see is that you're storing pointers returned by a readdir() call - you should copy the data contained therein instead.
Change
source = add_dir(source, insert_dir_node(entry->d_name, path));
to
source = add_dir(source, insert_dir_node(strdup(entry->d_name), path));
The reason you're seeing segmentation faults is that you always write after the end of the source->entries array.
You initially create a 0-size array:
int count = 0;
/* ... */
source->entries = (SRC_FILE **) malloc(sizeof(SRC_FILE*) * count);
Then set its 1st (indexed by 0) element:
source->entries[count]->entry = entry->d_name; //SEGFAULT HERE
count++;
source->entries = realloc(source->entries, sizeof(SRC_FILE*)*(count));
Then you expand the array to 1 element, then write to the second index, and so on.
You can either fix the logic (allocate space for count+1 elements always, because you want to have room not only for the existing ones but also for the next one), or, which in this case may be more efficient, switch to a linked list structure here as well.
The next problem is that you're only allocating pointers to SRC_FILE, not SRC_FILE structures - you should change the definition to:
struct SRC {
SRC_ERROR error;
char *name;
char *full_path;
SRC_FILE *entries;
SRC *next_dir;
};
And the initialization to
source->entries = (SRC_FILE *) malloc(sizeof(SRC_FILE) * (count + 1));
Then the critical part to
source->entries[count].entry = strdup(entry->d_name);
count++;
source->entries = realloc(source->entries, sizeof(SRC_FILE) * (count + 1));
There's one more thing to attend to: insert_dir_node creates a new SRC struct, which will need to have a freshly initialized entries member:
next_dir->count = 0;
next_dir->entries = (SRC_FILE *)malloc(sizeof(SRC_FILE) * (1));
and, since we have now separate entries we need to have a count for each of them, so move this variable into the struct as well.
Fixing all of these provided me with an error-free program.
The subject is Memory management in linked lists. Indeed this is a major issue in C program because there is no automatic memory management. You must decide and specify how each object pointed to by a pointer in your structures is handled from a memory management standpoint. Is the pointer the reference for the object life time or is the lifetime handled somewhere else and the pointer just an access point.
Let's analyse your object definitions:
typedef struct SRC_ERROR SRC_ERROR;
struct SRC_ERROR {
int error_code;
char *error;
};
SRC_ERROR is just a way to package an error description. If the error member always stores a pointer to a string literal, it should be defined as const char *. Conversely, if in some cases you allocate a string with information specific to the actual error, such as "error allocating 1023 objects\n", then you either need an indicator specifying the error points to allocated memory that should be freed after use or you should always allocate memory for the error message and always free this memory when discarding the SRC_ERROR object.
typedef struct SRC_FILE SRC_FILE;
struct SRC_FILE {
char *entry;
char md5[MD5_DIGEST_LENGTH];
};
entry should point to allocated memory and this memory should be freed when discarding the SRC_FILE object.
typedef struct SRC SRC; //Source file tree with md5 entry char for source verification.
struct SRC {
SRC_ERROR error;
char *name;
char *full_path;
SRC_FILE **entries;
SRC *next_dir;
};
name and full_path should point to allocated memory and should be freed when discarding the SRC object.
next_dir points to another SRC object, which should be allocated and freed consistently.
entries points to an allocated array, each element of which points to an allocated object. You need a way to tell the number of elements in this array. You could maintain a NULL pointer at the end of the array, but it is simpler to add a count member in SRC for this information. It would also be much simpler to make this a pointer to an allocated array of SRC objects.
The function does not construct a tree, but attempts to construct a list of directories. Whenever to recurse into a directory, you should append the new list from the SRC_ERROR object returned by scan_source to the list already constructed in the SRC_ERROR object allocated by the caller and free the object returned by the recursive call.
Here is a modified version in a test program:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
#define MD5_DIGEST_LENGTH 16
#define TRACE(x) //x
enum { OK = 0, ERROR, OUT_OF_MEMORY };
typedef struct ERROR_STATE ERROR_STATE;
struct ERROR_STATE {
int code;
const char *message; // always a string literal
};
typedef struct SRC_FILE SRC_FILE;
struct SRC_FILE {
char *name; // points to allocated memory
char md5[MD5_DIGEST_LENGTH];
};
typedef struct SRC SRC; //Source file tree with md5 entry char for source verification.
struct SRC {
char *name; // points to allocated memory
char *full_path; // points to allocated memory
size_t count; // number of elements in entries
SRC_FILE *entries; // allocated array of count elements
SRC *next_dir; // the next SRC
};
static char *basename_dup(const char *full_path) {
char *p = strrchr(full_path, '/');
return strdup(p ? p + 1 : full_path);
}
/* construct a SRC describing the directory contents.
* if there is an error, either return a partially constructed SRC or return NULL
*/
SRC *scan_source(const char *source_path, ERROR_STATE *error) {
char *full_path = strdup(source_path);
char *name = basename_dup(source_path);
SRC *source = calloc(1, sizeof(SRC)); // all members initialized to 0
if (source == NULL) {
error->code = ERROR;
error->message = "Unable to allocate memory.\n";
free(full_path);
free(name);
free(source);
return NULL;
}
error->code = OK;
source->full_path = full_path;
source->name = name;
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(source_path))) {
error->code = ERROR;
error->message = "Unable to open source directory.\n";
return source;
}
while ((entry = readdir(dir)) != NULL) {
char path[PATH_MAX];
int len;
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
len = snprintf(path, sizeof(path), "%s/%s", source_path, entry->d_name);
if (len >= (int)sizeof(path)) {
// the path was truncated.
// you can report this or ignore it...
TRACE(printf("[%s] - %s - path too long, ignored\n", entry->d_name, path));
continue;
}
if (entry->d_type == DT_DIR) {
TRACE(printf("[%s] - %s\n", entry->d_name, path));
SRC *source1 = scan_source(path, error);
if (error->code != OK) {
// either ignore the error or abort?
}
if (source1) {
// append the new directory (and its list of sub-directories)
SRC **tailp = &source->next_dir;
while (*tailp) tailp = &(*tailp)->next_dir;
*tailp = source1;
}
} else
if (entry->d_type == DT_REG) {
TRACE(printf("[FILE] - %s\n", entry->d_name));
// add the file to the entries list
SRC_FILE *entries = realloc(source->entries, sizeof(source->entries[0]) * (source->count + 1));
if (entries == NULL) {
// you should return to the caller with a proper error code
error->code = OUT_OF_MEMORY;
error->message = "cannot reallocate entries array";
break;
}
source->entries = entries;
// source->entries[count] must point to an allocated object
name = strdup(entry->d_name);
if (name == NULL) {
error->code = OUT_OF_MEMORY;
error->message = "cannot allocate entry name";
break;
}
source->entries[source->count].name = name;
memset(source->entries[source->count].md5, 0, sizeof(source->entries[source->count].md5));
source->count++;
//if (md5_sum(full_path, source->entries[source->count].md5)) {
// // error computing the MD5 sum...
//}
}
}
closedir(dir);
return source;
}
void free_source(SRC *source) {
if (source) {
free(source->name);
free(source->full_path);
for (size_t i = 0; i < source->count; i++) {
free(source->entries[i].name);
}
free(source);
}
}
int main(int argc, char *argv[1]) {
ERROR_STATE error = { 0, NULL };
if (argc < 2) {
printf("usage: scansource directory [...]\n");
return 1;
}
for (int i = 1; i < argc; i++) {
SRC *source = scan_source(argv[i], &error);
if (error.code) {
printf("Error %d: %s\n", error.code, error.message);
}
while (source) {
SRC *cur = source;
source = source->next_dir;
printf("{\n"
" name: '%s',\n"
" full_path: '%s',\n"
" count: %zu,\n"
" entries: [\n",
cur->name, cur->full_path, cur->count);
for (size_t j = 0; j < cur->count; j++) {
printf(" { md5: '");
for (size_t k = 0; k < MD5_DIGEST_LENGTH; k++)
printf("%02x", cur->entries[j].md5[k]);
printf("', name: '%s' },\n", cur->entries[j].name);
}
printf(" ]\n},\n");
free_source(cur);
}
}
return 0;
}
When ever I try to access data in memory that I've acquired using malloc, the data is corrupted
I'm writing a program that reads Linux directories and writes the names of the files and sub-directories in a "string array" (char** array in c). It operates using dirent.h functionalities like readdir(). readdir returns a dirent structure that has a dname[256] that's the name of a file/sub-directory in the target directory. I equate the dirent string(char*) to an index of a malloced position in a char** array
I basically have a walk_path() function that reads the directory entries and writes their names into a malloced location then return that location
data_t* walk_path(char* path) {
int size = 0;
if(path == NULL){
printf("NULL path\n");
return NULL;
}
struct dirent* entry;
DIR* dir_l = opendir(path);
if(dir_l == NULL) {
char** data = (char**)malloc(sizeof(char*) * 2);
data[0] = path;
data_t* ret = (data_t*)malloc(sizeof(data_t));
ret->data = data;
ret->size = 1;
return ret;
}
while((entry = readdir(dir_l)) != NULL) {
if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
size++;
}
closedir(dir_l);
char** data = (char**)malloc(sizeof(char*) * size + 1);
int loop_v = 0;
dir_l = opendir(path);
while((entry = readdir(dir_l)) != NULL && loop_v < size) {
if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
data[loop_v] = entry->d_name;
loop_v++;
}
closedir(dir_l);
data_t* ret = (data_t*)malloc(sizeof(data_t*));
ret->size = (size_t)size;
ret->data = data;
return ret;
}
and a merge path function that can take two directories and write their data into a single array
char** merge_path(char* path, char* path2) {
data_t* path_data = walk_path(path);
data_t* path2_data = walk_path(path2);
if(path_data == NULL || path2_data == NULL) {
printf("Merge failed, couldn't get path data\n");
return NULL;
}
char** new_dir_info = (char**)malloc(sizeof(char*) * (path2_data->size + path_data->size) );
if(new_dir_info == NULL)
return NULL;
int loop = 0;
while(loop < path_data->size) {
new_dir_info[loop] = path_data->data[loop];
loop++;
}
loop = 0;
while(loop < path2_data->size) {
new_dir_info[loop + path_data->size] = path2_data->data[loop];
loop++;
}
free(path_data);
free(path2_data);
return new_dir_info;
}
The char** array that the merge path function returns always has corrupted data, that is the characters in the character arrays are corrupted and not the pointers themselves, though I expect it to have the strings passed to it from the directory entries it instead has random strings.
I've stepped through the code and found that the data gets corrupted in merge path function, the source of the error could still originate from walk_path().
This
data_t* ret = (data_t*)malloc(sizeof(data_t*));
ought to be
data_t* ret = (data_t*)malloc(sizeof(data_t));
Generally in C void-pointers do not need to be casted, so all casts to malloc in your code can be dropped, which made the above line look like:
data_t* ret = malloc(sizeof(data_t*));
More over to rule out bugs like this one better step away from doubling the type to mallocate inside the call to malloc(), but better use the variable to allocate to along with the dereferencing operator, like this:
data_t* ret = malloc(sizeof *ret);
Also this line
data[loop_v] = entry->d_name;
copies a pointer to the entry name, not the name itself.
Consider using
data[loop_v] = strdup(entry->d_name);
which dynamically allocates room for a copy of where entry->d_name points to.
Alternatively instead of
char**data;
define
char (*data)[sizeof entry->d_name]; /* Array of pointers to char[as many char as entry->d_name is defined to have] */
or
char (*data)[sizeof ((struct dirent*)NULL)->d_name]; /* Array of pointers to char[as many char as entry->d_name is defined to have] */
and allocate to it like this (following the above proposed pattern):
data = malloc((size /* + 1 */) * sizeof *data); /* Not sure what the idea behind this +1 is. */
And instead of
data[loop_v] = strdup(entry->d_name);
do
strcpy(data[loop_v], entry->d_name);
If going this route you need to adjust the definition of data_t.data accordingly.
I've been trying to fix this for 2 days now. I really don't understand what is going on.
I wrote a function to read from a file line per line. It's called get_next_line(). This is a project for school, I'm only allowed to use my own C library and read(), malloc() and free().
Here is a link to the full GitHub repo, including the instructions. I can only submit get_next_line.c and get_next_line.h at the end.
Basically, my program seems to run fine if don't handle memory leaks. When I start fixing leaks by using my ft_strdel() function, my program still runs fine for most test cases.
void ft_strdel(char **as)
{
if (!as || !*as)
return ;
free(*as);
*as = 0;
}
Yet, if I pass it through the advanced unit test we have at school, I have different errors. They seem to appear randomly sometimes as I can pass 3 times in a row and get a malloc error the 4th time.
The current version seems to handle all memory leaks. Thus, I have this error from the filechecker:
get_next_line_tests(43165,0x7fff9e83d340) malloc: *
error for object 0x7fe0e3403748: incorrect checksum for freed object -
object was probably modified after being freed.
* set a breakpoint in malloc_error_break to debug
Here is the full function:
#include "get_next_line.h"
static t_list *get_current_node(const int fd, t_list **line_list)
{
t_list *tmp;
tmp = *line_list;
while (tmp)
{
if ((int)tmp->content_size == fd)
return (tmp);
tmp = tmp->next;
}
return (NULL);
}
static t_list *create_new_node(int fd, t_list **line_list)
{
t_list *new;
new = ft_lstnew("\0", fd);
ft_lstadd(line_list, new);
return (new);
}
char *read_until_newline(int fd)
{
char buf[BUFF_SIZE + 1];
char *tmp;
char *stack;
int ret;
if (read(fd, buf, 0) < 0)
return (NULL);
stack = ft_strnew(1);
if (!stack)
return (NULL);
while ((ret = read(fd, buf, BUFF_SIZE)) > 0)
{
buf[ret] = '\0';
tmp = ft_strjoin(stack, buf);
ft_strdel(&stack);
stack = tmp;
if (ft_strchr(buf, '\n'))
break;
}
return (stack);
}
int get_index_newline(char *str)
{
int i;
i = 0;
while (str[i] && str[i] != '\n')
i++;
return (i);
}
int get_next_line(const int fd, char **line)
{
static t_list *line_list;
t_list *current;
char *buffer;
char *tmp;
int index_newline;
if (fd < 0 || line == NULL)
return (-1);
current = get_current_node(fd, &line_list);
if (!current)
current = create_new_node(fd, &line_list);
if (!ft_strchr(current->content, '\n'))
buffer = read_until_newline(fd);
else
buffer = ft_strnew(1);
if (!buffer)
return (-1);
if (!ft_strlen(buffer) && !ft_strlen(current->content))
return (0);
tmp = current->content;
current->content = ft_strjoin(tmp, buffer);
if (tmp)
ft_strdel(&tmp);
if (buffer)
ft_strdel(&buffer);
index_newline = get_index_newline(current->content);
*line = ft_strsub(current->content, 0, index_newline);
tmp = current->content;
current->content = ft_strsub(tmp, index_newline + 1, ft_strlen(tmp) - index_newline - 1);
if (tmp)
ft_strdel(&tmp);
return (1);
}
Feel free to comment on any beginner error I could be doing in my code. Please note however that I have to follow a certain norm, that explain my variable declarations and initializations. I cannot use for loops. And have many restrictions like those.
Protecting my ft_strdel() calls with if statements was my last attent at solving the issue, obviously it failed and it's useless.
EDIT:
I handle multiple fd by using a static linked list. Each node contains a string with the last read buffer (whatever is left from the reading after substracting the most recently read line). And each node contains an int to store the fd it was read from. This way, I loop through the list until I find the corresponding fd to check for preexisting reading. If no node is returned, it means I have nothing stored for the specific fd so I'll just create a new node.
I hope you have answers! I'm not a regular user on Stack Overflow, I did my best to format my post and be specific. Tell me if I need to edit something.
And thanks for your help!
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
struct configurations *read_file(char * file_name)
{
FILE *f = fopen(file_name ,"r");
if(!f)
{
printf("**********Unable to open config.txt*********");
return NULL;
}
int i, prev, count;
char *line = NULL, buff[480] = {'\0'};
size_t len;
struct configurations *config = (struct configurations *) malloc(sizeof(struct configurations));
while (getline(&line,&len,f) != -1)
{
if(!strncmp("SERVERPORT = ",line,strlen("SERVERPORT = "))){
config->server_Port = atoi(strstr(line, " = ")+3);
}
else if(!strncmp("SCHEDULING = ",line,strlen("SCHEDULING = "))){
strcpy(config->sched,strstr(line, " = ") + 3);
}
By subctracting 1 from the length.
There are multiple simple and obvious improvements to your code
You should always check the return value before using from strstr().
strlen("SERVERPORT = ") is a very ugly way of writing 12, inefficient too.
You should use a little bit more white spaces to make the code readable.
Don't cast the return value of malloc() it only makes it more difficult to read and might hide a bug if you forget to include stdlib.h.
ALWAYS check if malloc() returned NULL before dereferencing the pointer.
Split every line at =, remove all surrounding white spaces from the 2 resulting values and then check which variable it is and assign the corresponding value.
As it is your code will fail if SERVERPORT=1234 for example, and even if it's ugly and spaces around the = operator are better, both should be valid unless of course you explicitly want the spaces.
Also by removing surrounding white spaces you ensure that any '\n' that was read by getline() will be removed from the value.
This is a quick API a wrote just now to show you how I would do it, of course every one has their own taste and ways to do things, but I hope it helps figuring out your mistakes
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
struct KeyValue {
char *key;
char *value;
};
struct KeyFile {
size_t size;
struct KeyValue *entries;
size_t count;
};
static struct KeyFile *
keyfile_new(void)
{
struct KeyFile *kf;
kf = malloc(sizeof(*kf));
if (kf == NULL)
return NULL;
kf->entries = malloc(10 * sizeof(*kf->entries));
if (kf->entries == NULL) {
kf->size = 0;
} else {
kf->size = 10;
}
kf->count = 0;
return kf;
}
static int
keyfile_add_value(struct KeyFile *kf, const char *const key, const char *const value)
{
struct KeyValue *entry;
if (kf->count + 1 >= kf->size) {
void *pointer;
pointer = realloc(kf->entries, (kf->size + 10) * sizeof(*kf->entries));
if (pointer == NULL)
return -1;
kf->entries = pointer;
kf->size += 10;
}
entry = &kf->entries[kf->count++];
entry->key = strdup(key);
entry->value = strdup(value);
return 0;
}
static void
keyfile_free(struct KeyFile *kf)
{
for (size_t i = 0 ; i < kf->count ; ++i) {
struct KeyValue *entry;
entry = &kf->entries[i];
free(entry->key);
free(entry->value);
}
free(kf->entries);
free(kf);
}
static struct KeyFile *
keyfile_read(const char *const path)
{
FILE *file;
struct KeyFile *kf;
size_t length;
char *line;
line = NULL;
length = 0;
file = fopen(path, "r");
if (file == NULL)
return NULL;
kf = keyfile_new();
if (kf == NULL)
return NULL;
while (getline(&line, &length, file) > 0) {
char *op;
char *key;
char *value;
op = strchr(line, '=');
if (op == NULL) {
fprintf(stderr, "malformed line!\n");
} else {
*op = '\0';
key = line;
while (isspace((unsigned char) *key) != 0)
++key;
value = op + 1;
op -= 1;
while (isspace((unsigned char) *op) != 0)
*(op--) = '\0';
while (isspace((unsigned char) *value) != 0)
value += 1;
op = value + strlen(value) - 1;
while (isspace((unsigned char) *op) != 0)
*(op--) = '\0';
if (keyfile_add_value(kf, key, value) != 0)
goto error;
}
}
fclose(file);
free(line);
return kf;
error:
keyfile_free(kf);
fclose(file);
free(line);
return NULL;
}
static void
keyfile_display(const struct KeyFile *const kf)
{
for (size_t i = 0 ; i < kf->count ; ++i) {
const struct KeyValue *entry;
entry = &kf->entries[i];
fprintf(stdout, "/%s/ => /%s/\n", entry->key, entry->value);
}
}
You could improve this to add lookup functions, to find specific values in the settings file. And you can make it a standalone library to use it in many projects too.
As part of learning C, I wrote the following code to combine directory name with file name. Eg: combine("/home/user", "filename") will result in /home/user/filename. This function is expected work across platforms (atleast on all popular linux distributions and windows 32 and 64bit).
Here is the code.
const char* combine(const char* path1, const char* path2)
{
if(path1 == NULL && path2 == NULL) {
return NULL;
}
if(path2 == NULL || strlen(path2) == 0) return path1;
if(path1 == NULL || strlen(path1) == 0) return path2;
char* directory_separator = "";
#ifdef WIN32
directory_separator = "\\";
#else
directory_separator = "/";
#endif
char p1[strlen(path1)]; // (1)
strcpy(p1, path1); // (2)
char *last_char = &p1[strlen(path1) - 1]; // (3)
char *combined = malloc(strlen(path1) + 1 + strlen(path2));
int append_directory_separator = 0;
if(strcmp(last_char, directory_separator) != 0) {
append_directory_separator = 1;
}
strcpy(combined, path1);
if(append_directory_separator)
strcat(combined, directory_separator);
strcat(combined, path2);
return combined;
}
I have the following questions regarding the above code.
Consider the lines numbered 1,2,3. All those 3 lines are for getting the last element from the string. It looks like I am writing more code for such a small thing. What is the correct method to get the last element from the char* string.
To return the result, I am allocating a new string using malloc. I am not sure this is the right way to do this. Is caller expected to free the result? How can I indicate the caller that he has to free the result? Is there a less error prone method available?
How do you rate the code (Poor, Average, Good)? What are the areas that can be imrpoved?
Any help would be great.
Edit
Fixed all the issues discussed and implemented the changes suggested. Here is the updated code.
void combine(char* destination, const char* path1, const char* path2)
{
if(path1 == NULL && path2 == NULL) {
strcpy(destination, "");;
}
else if(path2 == NULL || strlen(path2) == 0) {
strcpy(destination, path1);
}
else if(path1 == NULL || strlen(path1) == 0) {
strcpy(destination, path2);
}
else {
char directory_separator[] = "/";
#ifdef WIN32
directory_separator[0] = '\\';
#endif
const char *last_char = path1;
while(*last_char != '\0')
last_char++;
int append_directory_separator = 0;
if(strcmp(last_char, directory_separator) != 0) {
append_directory_separator = 1;
}
strcpy(destination, path1);
if(append_directory_separator)
strcat(destination, directory_separator);
strcat(destination, path2);
}
}
In the new version, caller has to allocate enough buffer and send to combine method. This avoids the use of malloc and free issue. Here is the usage
int main(int argc, char **argv)
{
const char *d = "/usr/bin";
const char* f = "filename.txt";
char result[strlen(d) + strlen(f) + 2];
combine(result, d, f);
printf("%s\n", result);
return 0;
}
Any suggestions for more improvements?
And there is a memory leak:
const char *one = combine("foo", "file");
const char *two = combine("bar", "");
//...
free(one); // needed
free(two); // disaster!
Edit: Your new code looks better. Some minor stylistic changes:
Double semi-colon ;; in line 4.
In line 6, replace strlen(path2) == 0 with path2[0] == '\0'' or just !path2[0].
Similarly in line 9.
Remove loop determining last_char, and use const char last_char = path1[strlen(path1) - 1];
Change if(append_directory_separator) to if(last_char != directory_separator[0]). And so you don't need the variable append_directory_separator any more.
Have your function also return destination, similar to strcpy(dst, src), which returns dst.
Edit: And your loop for last_char has a bug: it always returns the end of path1, and so you could end up with a double slash // in your answer. (But Unix will treat this as a single slash, unless it is at the start). Anyway, my suggestion fixes this--which I see is quite similar to jdmichal's answer. And I see that you had this correct in your original code (which I admit I only glanced at--it was too complicated for my taste; your new code is much better).
And two more, slightly-more subjective, opinions:
I would use stpcpy(), to avoid the inefficiency of strcat(). (Easy to write your own, if need be.)
Some people have very strong opinions about strcat() and the like as being unsafe. However, I think your usage here is perfectly fine.
The only time you use last_char is in the comparision to check if the last character is a separator.
Why not replace it with this:
/* Retrieve the last character, and compare it to the directory separator character. */
char directory_separator = '\\';
if (path1[strlen(path1) - 1] == directory_separator)
{
append_directory_separator = 1;
}
If you want to account for the possibility of multiple character separators, you can use the following. But be sure when allocating the combined string to add strlen(directory_separator) instead of just 1.
/* First part is retrieving the address of the character which is
strlen(directory_separator) characters back from the end of the path1 string.
This can then be directly compared with the directory_separator string. */
char* directory_separator = "\\";
if (strcmp(&(path1[strlen(path1) - strlen(directory_separator)]), directory_separator))
{
append_directory_separator = 1;
}
The less error-prone method would be to have the user give you the destination buffer and its length, much the way strcpy works. This makes it clear that they must manage allocating and freeing the memory.
The process seems decent enough. I think there's just some specifics that can be worked on, mostly with doing things in a clunky way. But you are doing well, in that you can already recognize that happening and ask for help.
This is what I use:
#if defined(WIN32)
# define DIR_SEPARATOR '\\'
#else
# define DIR_SEPARATOR '/'
#endif
void combine(char *destination, const char *path1, const char *path2) {
if (path1 && *path1) {
auto len = strlen(path1);
strcpy(destination, path1);
if (destination[len - 1] == DIR_SEPARATOR) {
if (path2 && *path2) {
strcpy(destination + len, (*path2 == DIR_SEPARATOR) ? (path2 + 1) : path2);
}
}
else {
if (path2 && *path2) {
if (*path2 == DIR_SEPARATOR)
strcpy(destination + len, path2);
else {
destination[len] = DIR_SEPARATOR;
strcpy(destination + len + 1, path2);
}
}
}
}
else if (path2 && *path2)
strcpy(destination, path2);
else
destination[0] = '\0';
}
Maybe I'm a bit late to this, but I improved the updated code in a way, that it also works with something like this "/../".
/*
* Combine two paths into one. Note that the function
* will write to the specified buffer, which has to
* be allocated beforehand.
*
* #dst: The buffer to write to
* #pth1: Part one of the path
* #pth2: Part two of the path
*/
void joinpath(char *dst, const char *pth1, const char *pth2)
{
if(pth1 == NULL && pth2 == NULL) {
strcpy(dst, "");
}
else if(pth2 == NULL || strlen(pth2) == 0) {
strcpy(dst, pth1);
}
else if(pth1 == NULL || strlen(pth1) == 0) {
strcpy(dst, pth2);
}
else {
char directory_separator[] = "/";
#ifdef WIN32
directory_separator[0] = '\\';
#endif
const char *last_char = pth1;
while(*last_char != '\0')
last_char++;
int append_directory_separator = 0;
if(strcmp(last_char, directory_separator) != 0) {
append_directory_separator = 1;
}
strcpy(dst, pth1);
if(append_directory_separator)
strcat(dst, directory_separator);
strcat(dst, pth2);
}
char *rm, *fn;
int l;
while((rm = strstr (dst, "/../")) != NULL) {
for(fn = (rm - 1); fn >= dst; fn--) {
if(*fn == '/') {
l = strlen(rm + 4);
memcpy(fn + 1, rm + 4, l);
*(fn + len + 1) = 0;
break;
}
}
}
}
Just a little remark in order to improve your function:
Windows does support both '/' and '\\' separators in paths. So I should be able to perform the following call:
const char* path1 = "C:\\foo/bar";
const char* path2 = "here/is\\my/file.txt";
char destination [ MAX_PATH ];
combine ( destination, path1, path2 );
An idea when writing a multiplatform project could be to convert '\\' to '/' in any input path (from user input, loaded files...), then you will only have to deal with '/' characters.
Regards.
A quick glance shows:
you are using C++ comments (//) which is not standard C
you are declaring variables part way down the code - also not C. They should be defined at the start of the function.
your string p1 at #1 has 1 too many bytes written to it at #2 because strlen returns the length of a string and you need 1 more byte for the null terminator.
the malloc does not allocate enough memory - you need length of path1 + length of path2 + length of separator + null terminator.