I am relatively new to C and malloc. I wrote a lib with basic functions that I am filling with new ones as I go and that I am using for other small projects like this one.
I heard about Valgrind and decided to check my program with it but can't quite understand why I have so many leaks, I feel like all my mallocs are protected with if (line == NULL) when using functions using malloc themselves.
Could you guys put me back on tracks here ?
static char *concator(char *s1, char *s2, size_t len)
{
char *line;
size_t size;
if (!s1 || !s2)
return (NULL);
size = strlen(s1) + strlen(s2);
line = (char*)memalloc(sizeof(char) * size + 1);
if (line == NULL)
return (NULL);
strcpy(line, s1);
strncat(line, s2, len);
strdel(&s1);
return (line);
}
int line_reader(const int fd, char **line)
{
static char buf[BUFF_SIZE];
char *pos;
int ret;
if (fd < 0 || !line || read(fd, buf, 0) < 0 || BUFF_SIZE < 1)
return (-1);
*line = strnew(0);
if (line == NULL)
return (-1);
while (1)
{
pos = strchr(buf, '\n');
if (pos)
{
*line = concator(*line, buf, pos - buf);
if (line == NULL)
return (-1);
strncpy(buf, &buf[pos - buf + 1], BUFF_SIZE - (pos - buf));
return (1);
}
*line = concator(*line, buf, BUFF_SIZE);
if (line == NULL)
return (-1);
ret = read(fd, buf, BUFF_SIZE);
buf[ret] = '\0';
if (!ret)
return ((**line) ? 1 : 0);
}
}
Free line after you're done using it, like:
char* line;
int rc = line_reader(fd, &line);
if(rc > 0)
{
//use line
printf("%s\n", line);
//then free it
free(line);
}
This should fix the memory leak problems reported by valgrind. However watch out for other errors mentioned in the comments.
Related
As an exercise i'm writing a C function to read content from a file descriptor one line at a time.
In the exercise i'm only allowed to use read(), malloc() and free() from the standard library, while using one static variable. The function works but i keep getting a persistent memory leak when i reach the last line in the file that i cant seem to solve.
Leak:
Direct leak of 1 byte(s) in 1 object(s) allocated from:
#0 0x7f37f253a4bf in __interceptor_malloc (/usr/lib/gcc/x86_64-pc-linux-gnu/12/libasan.so.8+0xbd4bf)
#1 0x5644d5df4735 in offset /home/practical/Documents/Code/42/get_next_line/get_next_line.c:71
#2 0x5644d5df49ba in get_next_line /home/practical/Documents/Code/42/get_next_line/get_next_line.c:116
#3 0x5644d5df4a8b in main /home/practical/Documents/Code/42/get_next_line/get_next_line.c:138
#4 0x7f37f22cd189 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: 1 byte(s) leaked in 1 allocation(s).
Code:
#include "get_next_line.h"
#include <stdlib.h>
#include <unistd.h>
// needed for main:
#include <stdio.h>
#include <fcntl.h>
// finds the first line ending in \n from buffer
// this is the line for get_next_line to return
char *next_line(char *a)
{
int i;
char *buf;
if (!a || !a[0])
return (NULL);
i = 0;
while (a[i] && a[i] != '\n')
i++;
if (a[i] == '\n')
i++;
buf = (char *)malloc((sizeof(char) * i) + 1);
if (!buf)
return (NULL);
i = 0;
while (a[i] && a[i] != '\n')
{
buf[i] = a[i];
i++;
}
if (a[i] == '\n')
buf[i++] = '\n';
buf[i] = '\0';
return (buf);
}
// finds the end of the line we just returned in the buffer
// moves it to the end of new buffer
// changes the \n before it to '\0', so our new buffer does not contain it
// this offsets our buffer, the buffer now starts
// at the next line to be printed
char *offset(char *a)
{
char *buf;
int i;
int x;
i = 0;
x = 0;
while (a[i] && a[i] != '\n')
i++;
if (a[i] == '\0')
{
free(a);
return (NULL);
}
if (a[i] == '\n')
i++;
buf = (char *)malloc(ft_strlen(a) - i + 1);
if (!buf)
return (NULL);
while (a[i + x])
{
buf[x] = a[i + x];
x++;
}
buf[x] = '\0';
free(a);
return (buf);
}
// static buffer str starts at the next line to be printed
// we use strchr to find if there is a \n in the buffer
// if theres not, we read from the file into the buffer
// if \n is present, this is the line to print
// next_line then gets the first line ending in '\n' from the static buffer
// we return this line
// then we offset the static buffer by the line we just returned
char *get_next_line(int fd)
{
static char *str;
char *buf;
int i;
if (fd < 0 || BUFFER_SIZE <= 0)
return (NULL);
buf = (char *)malloc((sizeof(char) * BUFFER_SIZE) + 1);
if (!buf)
return (NULL);
i = 1;
while (!(ft_strchr(str, '\n')) && i != 0)
{
i = read(fd, buf, BUFFER_SIZE);
if (i == -1)
{
free(buf);
return (NULL);
}
buf[i] = '\0';
str = ft_strjoin(str, buf);
}
free(buf);
buf = next_line(str);
str = offset(str);
return (buf);
}
int main(int argc, char *argv[])
{
char *line;
int fd;
if (argc < 2)
{
printf("Usage: %s <file>\n", argv[0]);
return (1);
}
fd = open(argv[1], O_RDONLY);
if (fd == -1)
{
perror("Failed to open file");
return (1);
}
while (line != NULL)
{
line = get_next_line(fd);
printf("%s", line);
free(line);
}
close (fd);
return (0);
}
ft_strjoin
char *ft_strdup(char *src)
{
char *dst;
int i;
i = 0;
dst = malloc(ft_strlen(src) + 1);
if (!dst)
return (NULL);
while (src[i])
{
dst[i] = src[i];
i++;
}
dst[i] = '\0';
return (dst);
}
char *ft_strcat(char *dest, char *src)
{
int i;
int l;
i = 0;
l = ft_strlen(dest);
while (src[i] != '\0')
{
dest[i + l] = src[i];
i++;
}
dest[i + l] = '\0';
return (dest);
}
char *ft_strjoin(char const *s1, char const *s2)
{
char *str;
size_t len;
if (!s1 && !s2)
return (ft_strdup(""));
if (s1 && !s2)
return (ft_strdup((char *)s1));
if (!s1 && s2)
return (ft_strdup((char *)s2));
len = ft_strlen((char *)s1) + ft_strlen((char *)s2);
str = malloc(sizeof(char) * (len + 1));
if (!str)
return (NULL);
str[0] = '\0';
ft_strcat(str, (char *)s1);
ft_strcat(str, (char *)s2);
return (str);
}
Problem
I went over the code and it seems that i am freeing everything.
I think the issue may be with returning buf in the get_next_line function, which gets allocated by the next_line function. I cant free this because i am returning it, but i am freeing the string later in the main().
Maybe i should be freeing the static variable after i reach the end of the file i'm reading from the descriptor?
Any suggestions appreciated, thanks
"seems that i am freeing everything" --> code does not free str in get_next_line() when done.
I'm new to C and still don't really know how to work with valgrind. I'm doing a project where i need to create a function that returns a line of text from a file descriptor each time it's called using just one static variable.
Repeated calls (e.g., using a loop) to your get_next_line() function should let
you read the text file pointed to by the file descriptor, one line at a time.
I have come up with this but I can't find where the memory leak is:
char *output(char **backup, char *rbackup, int ret, int fd)
{
int value;
char *temp;
if (ret < 0)
return (NULL);
else if (ret == 0 && backup[fd] == NULL)
return (NULL);
value = (int)(ft_strchr(backup[fd], '\n') - backup[fd] + 1);
rbackup = ft_substr(backup[fd], 0, value);
temp = ft_substr(backup[fd], value, BUFFER_SIZE * BUFFER_SIZE);
free(backup[fd]);
if (temp[0] == '\0')
{
free(temp);
temp = NULL;
}
backup[fd] = temp;
return (rbackup);
}
char *get_next_line(int fd)
{
int ret;
char buf[BUFFER_SIZE + 1];
static char *backup[NUM_OF_FD];
char *rbackup;
if (fd < 0 || fd > NUM_OF_FD)
return (NULL);
while (ft_strchr(backup[fd], '\n') == NULL)
{
ret = read(fd, buf, BUFFER_SIZE);
buf[ret] = '\0';
if (ret <= 0)
break ;
if (backup[fd] == NULL)
backup[fd] = ft_strdup(buf);
else
{
rbackup = ft_strjoin(backup[fd], buf);
free(backup[fd]);
backup[fd] = rbackup;
}
}
return (output(backup, rbackup, ret, fd));
}
The ft_functions are equivalent to the LibC counterparts but in case of having a bug I'll post them here:
void *ft_memcpy(void *dst, const void *src, size_t n)
{
size_t i;
i = -1;
if ((dst != src) && n)
while (++i < n)
((unsigned char *)dst)[i] = ((unsigned char *)src)[i];
return (dst);
}
size_t ft_strlen(const char *s)
{
size_t i;
i = 0;
while (s[i])
{
i++;
}
return (i);
}
char *ft_strchr(const char *s, int c)
{
char chr;
chr = (char)c;
if (s == NULL)
return (NULL);
while (*s && *s != chr)
s++;
if (*s == chr)
return ((char *)s);
else
return (NULL);
}
char *ft_substr(char const *s, unsigned int start, size_t len)
{
char *str;
if (!s)
return (NULL);
if (len > ft_strlen(s))
len = ft_strlen(s);
if (start > ft_strlen(s))
len = 0;
str = malloc(sizeof(char) * (len + 1));
if (!str)
return (NULL);
str = ft_memcpy(str, &s[start], len);
str[len] = '\0';
return (str);
}
char *ft_strdup(const char *s1)
{
size_t len;
void *new;
len = ft_strlen(s1) + 1;
new = malloc(len);
if (new == NULL)
return (NULL);
return ((char *) ft_memcpy(new, s1, len));
}
char *ft_strjoin(char const *s1, char const *s2)
{
int i;
char *str;
size_t size;
if (!s1 || !s2)
return (NULL);
i = 0;
size = (ft_strlen(s1) + ft_strlen(s2) + 1);
str = malloc(sizeof(char) * size);
if (!str)
return (NULL);
while (*s1)
str[i++] = *s1++;
while (*s2)
str[i++] = *s2++;
str[i] = '\0';
return (str);
}
void *ft_memset(void *b, int c, size_t len)
{
size_t i;
i = -1;
while (++i < len)
((unsigned char *)b)[i] = (unsigned char)c;
return (b);
}
Is there any rookie mistake in my code?
Well I ran your code like this
int main(int a, char**b)
{
int f = open("poop.c", O_RDONLY);
for(int i = 0; i < 10; i++)
{
char *x = get_next_line(f);
printf("x");
free(x);
}
}
reading the first 10 lines. No leaks detected by valgrind (once I added the free in the loop)
It did moan about other things though
==3695== Invalid read of size 1
==3695== at 0x109270: ft_memcpy (poop.c:26)
==3695== by 0x1093BB: ft_substr (poop.c:70)
==3695== by 0x109619: output (poop.c:120)
==3695== by 0x109802: get_next_line (poop.c:155)
==3695== by 0x109857: main (poop.c:162)
==3695== Address 0x4a4a0a5 is 0 bytes after a block of size 101 alloc'd
==3695== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3695== by 0x109408: ft_strdup (poop.c:81)
==3695== by 0x109724: get_next_line (poop.c:147)
==3695== by 0x109857: main (poop.c:162)
==3695==
==3695== Conditional jump or move depends on uninitialised value(s)
==3695== at 0x109546: output (poop.c:114)
==3695== by 0x109802: get_next_line (poop.c:155)
==3695== by 0x109857: main (poop.c:162)
==3695==
==3695== Conditional jump or move depends on uninitialised value(s)
==3695== at 0x109556: output (poop.c:116)
==3695== by 0x109802: get_next_line (poop.c:155)
==3695== by 0x109857: main (poop.c:162)
==3695==
seems like the uninitialized read is caused by reading an empty line.
The invalid read is here
((unsigned char*)dst)[i] = ((unsigned char*)src)[i];
If you have Makefile for the C file, then you can add -g after flags in your Makefile. If you use compile the C file by clang or gcc directly, you can put -g after clang or gcc. Then you can get more information about your problem. -g is used for debugging. If you don't add that, Valgrind will only tell you which part you need to debug (like a specific function). But if you add -g, you will be able to know which line you need to debug with.
You definitely have a problem with:
static char *backup[NUM_OF_FD];
You check that fd is larger than NUM_OF_FD, but NUM_OF_FD would still try to index outside of the backup array.
if (fd < 0 || fd > NUM_OF_FD)
return (NULL);
should be
if (fd < 0 || fd >= NUM_OF_FD)
return (NULL);
Also be aware that read() can return -1, so this code could be problematic:
ret = read(fd, buf, BUFFER_SIZE);
buf[ret] = '\0';
if (ret <= 0)
break ;
Check the return value before using it.
hello guys a just need help on this, not showing the text I wrote:
This program open the file and just show on command what is inside,
if buffer is > 0 show all the text contained in file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
char *ft_strncat(char *dst, const char *src, size_t n)
{
if (n != 0) {
char *d = dst;
const char *s = src;
while (*d != 0)
d++;
do {
if ((*d = *s++) == 0)
break;
d++;
} while (--n != 0);
*d = 0;
}
return (dst);
}
char *get_next_line(int fd)
{
char buffer[2] = "";
char **line;
if( !*line )
*line = malloc(100 * sizeof(char));
*line[0] = '\0';
while( read(fd, buffer, 1) > 0 ) {
ft_strncat(*line, buffer, 1);
if( buffer[0] == '\n' )
break;
}
return (0);
}
int main(void)
{
int fd;
int ret;
fd = open("ola.txt", O_RDONLY);
if (fd < 3 && fd != 0)
return (-1);
printf("%d\n", fd);
printf("%s\n", get_next_line(fd));
return (0);
}
im trying to see the error but I cant, im a noob on C yet
thank you for help me.
line should be char *, not char **. That would only be needed if it were a function parameter that should be updated by the function.
You need to return line from the function, not 0.
You should use realloc() to grow line if the input line is longer than the size of line. Use a variable capacity to hold the current size.
There's no good reason to use ft_strncat(). Use another variable to hold the current position in line, and write the character there directly.
char *get_next_line(int fd)
{
char buffer;
size_t capacity = 100;
char *line = malloc(capacity * sizeof(char));
size_t pos = 0;
*line[0] = '\0';
while( read(fd, &buffer, 1) > 0 ) {
if (pos > capacity - 2) {
capacity += 100;
line = realloc(line, capacity);
}
line[pos++] = buffer;
if( buffer == '\n' ) {
line[pos] = '\0';
break;
}
}
return line;
}
In addition, the caller should assign the result to a variable, so it can free the memory. Otherwise you'll create lots of memory leaks when you read all the lines of the file.
In this function, I have leak of memory
static int read_buffer(int const fd, char **buffer)
{
char buff[BUFF_SIZE + 1];
int ret;
ret = read(fd, buff, BUFF_SIZE);
if (ret > 0)
{
buff[ret] = 0;
if (!(*buffer = ft_strjoin(*buffer, buff)))
return (-1);
}
return (ret);
}
I tried to do this and more solution again.. But I get also leak of memory
static int read_buffer(int const fd, char **buffer)
{
char buff[BUFF_SIZE + 1];
char *tmp;
int ret;
ret = read(fd, buff, BUFF_SIZE);
tmp = *buffer;
if (ret > 0)
{
buff[ret] = 0;
if (!(*buffer = ft_strjoin(*buffer, buff)))
return (-1);
free(tmp);
}
return (ret);
}
Here is ft_strjoin function: (can't modify)
char *ft_strjoin(char const *s1, char const *s2)
{
size_t size_s1;
size_t size_s2;
char *strjoin;
size_s1 = ft_strlen(s1);
size_s2 = ft_strlen(s2);
if (!(strjoin = malloc(size_s1 + size_s2 + 1)))
return (NULL);
ft_strcpy(strjoin, s1);
ft_strcat(strjoin, s2);
return (strjoin);
}
How I can resolve this ?
Thank you !
Anytime a function returns malloc'd memory, you've got a hot potato. You have to free that memory and cannot pass it on unless you've documented you're doing so. And you mustn't lose hold of (e.g. reuse) the pointer before freeing it. Below is how I interpret how read_buffer() needs to work. Since "buffer length is unknown" I'm assuming it comes from malloc and we can use realloc:
static int read_buffer(int const fd, char **buffer)
{
char local_buffer[BUFF_SIZE + 1];
int bytes_read = read(fd, local_buffer, BUFF_SIZE);
if (bytes_read > 0)
{
local_buffer[bytes_read] = '\0';
char *joined = ft_strjoin(*buffer, local_buffer);
if (joined != NULL)
{
*buffer = realloc(*buffer, strlen(joined) + 1);
if (*buffer != NULL)
{
ft_strcpy(*buffer, joined);
}
else
{
bytes_read = -1;
}
free(joined);
}
else {
bytes_read = -1;
}
}
return bytes_read;
}
Note that there are a lot of potential pitfalls that you need to test for and recover from.
If you're reading from the user directly, and don't want newlines as part of the concatenation of strings, you can replace this line:
local_buffer[bytes_read] = '\0';
with something like:
if (local_buffer[bytes_read - 1] == '\n')
{
local_buffer[bytes_read - 1] = '\0';
}
else
{
local_buffer[bytes_read] = '\0';
}
Assuming the above newline fix, here's a small test program I wrote:
int main() {
char *buffer = malloc(1);
*buffer = '\0';
for (int i = 0; i < 3; i++)
{
(void) read_buffer(0, &buffer);
puts(buffer);
}
free(buffer);
return 0;
}
RESULT
> ./a.out
abcdefg
abcdefg
hijklmn
abcdefghijklmn
opqrstu
abcdefghijklmnopqrstu
>
I wish to get strings from the buffer of raw bytes in the memory, will work well?
static int in = 0;
void *loadFile (FILE *fp)
{
fseek (fp, 0L, SEEK_END);
size_t size = ftell (fp);
fseek (fp, 0L, SEEK_SET);
char *buf = malloc (sizeof(char) * size);
if (!buf)
return NULL;
if (fread (buf, sizeof(char), size, fp) != size) {
free (buf);
return NULL;
}
return buf;
}
char *getString (void *buf)
{
char *l_buf = buf;
int i, j, num;
char *string = NULL;
for (i = in; l_buf[i] == '\n' || l_buf[i] == '\r'; i++);
for (j = i; l_buf[j] != '\n' && l_buf[j] != '\r'; j++);
num = j - i;
string = malloc (sizeof(char) * (num + 1));
if (!string)
return NULL;
in = j;
strncpy (string, &l_buf[i], num);
string[num] = '\0';
return string;
}
I believe there is at least one problem with the solution as proposed and that is there is no check to ensure you don't run off the end of the memory buffer in getString(). So one way to avoid this in your read code would be to add an explicit NULL to the end of the buffer like so
char *buf = malloc (sizeof(char) * (size + 1));
if (!buf)
return NULL;
if (fread (buf, sizeof(char), size, fp) != size) {
free (buf);
return NULL;
}
buf[size] = `\0`;
And then in your string extraction function add the a NULL check to the line termination tests, something like this:
for (i = in; l_buf[i] != '\0' && (l_buf[i] == '\n' || l_buf[i] == '\r'); i++);
if (l_buf[i] == '\0') {
/* Never saw the start of a line before the buffer ran out */
return NULL;
}
for (j = i; l_buf[i] != '\0' && l_buf[j] != '\n' && l_buf[j] != '\r'; j++);
if (i == j) {
return NULL;
}
There is another potential problem but as you didn't say whether you were running on UNIX or Windows or cared about portability here I can't be sure. The proposed code doesn't deal with line terminations that include both `\r' and '\n'.
I would also suggest to make the function re-entrant by replacing the global start position index with a parameter like so:
char *getString (void *buf, int *in) { ...
Then just update that pointer in getString() like so:
*in = j;
All references to buf[i] should be l_buf[i]. buf[i] is indexing from a void pointer (not what you want), but l_buf [i] is indexing from a char pointer.