This question already has answers here:
Facing an error "*** glibc detected *** free(): invalid next size (fast)"
(2 answers)
Closed 8 years ago.
When I compile and run this code, I get an error. The error message is:
realloc(): invalid next size: 0x0000000002119010
The file input has about 4000 words.
I debugged it, but I can not find any problem.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define BUF_LEN 10 //buf length
int main(int argc, char *argv[])
{
int file_d; //file descriptor
char *ct_head; //content_first_char
char *content;
ssize_t read_len = BUF_LEN; //had read length
int mem_block_count = 0;
file_d = open("input", O_RDONLY);
if (file_d < 0)
{
perror("open");
exit (1);
}
content = ct_head = (char *) malloc(sizeof(char) * BUF_LEN);
mem_block_count = 1;
while (read_len == BUF_LEN)
{
read_len = read(file_d, ct_head, BUF_LEN);
if (read_len < 0)
{
perror("read");
exit(2);
}
if (read_len == BUF_LEN)
{
ct_head = (char *)realloc(content, sizeof(char) *(++mem_block_count));
ct_head = &content[(mem_block_count-1) * BUF_LEN];
}
else
ct_head[read_len] = '\0';
}
printf("%s", content);
close(file_d);
free(content);
return 0;
}
I'm not sure what your problem is but these lines:
ct_head = (char *)realloc(content, sizeof(char) *(++mem_block_count));
ct_head = &content[(mem_block_count-1) * BUF_LEN];
Are very dodgy. After the first line, ct_head points to the realloced block and content points to garbage. Then the second line reads content and re-assigns ct_head - leaking the realloced memory.
I suspect you may just have memory corruption in your program?
I think
ct_head = (char *)realloc(content, sizeof(char) *(++mem_block_count));
should be:
content = (char *)realloc(content, sizeof(char) *(++mem_block_count) * BUF_LEN);
if (content == NULL)
{
// do something if the realloc fails
}
The first time you use realloc, it allocates only 2 bytes because, going into the call, sizeof(char) is 1 and mem_block_count is also 1 (and it is then pre-incremented to 2).
This means the next read will overrun the buffer it has allocated. I suspect you need to also multiply by BUF_LEN in your realloc.
Edit
I've just realised, it's even worse: straight after allocating content to 2 bytes, you set ct_head to BUF_LEN bytes beyond the start of content. This means your read overwrites an area that is totally outside the buffer.
You've already got some good answers here, but here's a little bit of advice that won't fit in a comment. In C, sizeof(char) is always 1. It is 1 by definition, so it is as redundant as using (1 * BUF_LEN). Also in C, you don't have to [and shouldn't] cast the result of malloc. Doing so can mask a fundamental error1.
If you want to allocate space depending on the size of a type, use:
ct_head = malloc(sizeof(*ct_head) * BUF_LEN);
That way, if the type of ct_head changes, you'll still be allocating enough space, without having to change all the calls to malloc and realloc.
1. If you do not include the appropriate header for malloc, then a C compiler will assume that malloc returns int, and this may cause issues on platforms where the size of an int differs from the size of a pointer type. Also, conversions from integer types to pointer types are implementation-defined.
Related
While searching through this board for information about reading a full file into memory using C, I came across a use of fread() that I haven't seen before. I'm trying to understand it.
My questions are:
Is there a name/term for what is being done here?
What is happening when the size_t used is being added to the char *data and how is this considered a valid void *ptr by fread?
I'm going to put the code from the author's post in here and I'll link to the post as well. Unfortunately, the post is old, locked, and I don't have enough points here to leave a comment asking for clarification on it.
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
/* Size of each input chunk to be
read and allocate for. */
#ifndef READALL_CHUNK
#define READALL_CHUNK 262144
#endif
#define READALL_OK 0 /* Success */
#define READALL_INVALID -1 /* Invalid parameters */
#define READALL_ERROR -2 /* Stream error */
#define READALL_TOOMUCH -3 /* Too much input */
#define READALL_NOMEM -4 /* Out of memory */
/* This function returns one of the READALL_ constants above.
If the return value is zero == READALL_OK, then:
(*dataptr) points to a dynamically allocated buffer, with
(*sizeptr) chars read from the file.
The buffer is allocated for one extra char, which is NUL,
and automatically appended after the data.
Initial values of (*dataptr) and (*sizeptr) are ignored.
*/
int readall(FILE *in, char **dataptr, size_t *sizeptr)
{
char *data = NULL, *temp;
size_t size = 0;
size_t used = 0;
size_t n;
/* None of the parameters can be NULL. */
if (in == NULL || dataptr == NULL || sizeptr == NULL)
return READALL_INVALID;
/* A read error already occurred? */
if (ferror(in))
return READALL_ERROR;
while (1) {
if (used + READALL_CHUNK + 1 > size) {
size = used + READALL_CHUNK + 1;
/* Overflow check. Some ANSI C compilers
may optimize this away, though. */
if (size <= used) {
free(data);
return READALL_TOOMUCH;
}
temp = realloc(data, size);
if (temp == NULL) {
free(data);
return READALL_NOMEM;
}
data = temp;
}
n = fread(data + used, 1, READALL_CHUNK, in);
if (n == 0)
break;
used += n;
}
if (ferror(in)) {
free(data);
return READALL_ERROR;
}
temp = realloc(data, used + 1);
if (temp == NULL) {
free(data);
return READALL_NOMEM;
}
data = temp;
data[used] = '\0';
*dataptr = data;
*sizeptr = used;
return READALL_OK;
}
Link: C Programming: How to read the whole file contents into a buffer
What is happening when the size_t used is being added to the char *data and how is this considered a valid void *ptr by fread?
In practice(*), a pointer is just a number, which references an address in (virtual) memory. What's being done here is simple pointer arithmetic: You can add an integer to a pointer, which increases its value, so if your pointer pointed to address 1000 and you add 20, it now points to address 1020. Since used is always the number of bytes read so far, you point this many bytes into the data buffer.
But there's one more thing: This only works as described if the data type of the pointer has a size of 1 byte (as char does(*)). Because when you do pointer arithmetic, you don't increase the pointer by that many bytes, but really by multiples of the data type's size, so you always end up pointing to the start of an element in your array, and not somewhere in the middle if you're dealing with int. I.e. if you have int *x which points to address 1000, and you do x += 20, then x will point to address 1080 now, which is where x[20] would be located.
and how is this considered a valid void *ptr by fread?
Considering "pointers are just numbers", fread doesn't care how you arrived at that pointer value. As long as there is valid memory to write to, it will happily accept whatever you pass it.
(*) Assuming a modern architecture accessible by mere mortals.
This question already has answers here:
How to qsort an array of pointers to char in C?
(9 answers)
C library function to perform sort
(9 answers)
Dynamically create an array of strings with malloc
(4 answers)
Closed 5 years ago.
So I'm trying to make a program that takes in certain number of strings from stdin and then output into a text file.
The code I have so far is:
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int cmpstr(const void * a, const void *b){
const char* aa = (const char*) a;
const char* bb = (const char*) b;
return strcmp (aa, bb);
}
int main(int argc, char *argv[]){
int i =0;
int scount;
char ** data = NULL;
FILE * ofile;
if (argc != 3){
printf("%s \n", "The format to use this is: mySort <#of strings> <output filename>",argv[0]);
exit(EXIT_FAILURE);
}
scount = atoi(argv[1]);
if(!scount){
printf("%s \n", "Invalid number.");
exit(EXIT_FAILURE);
}
data = (char **) malloc(scount * sizeof(char*));
if(NULL == data){
printf("Memory allocation failed\n");
exit(EXIT_FAILURE);
}
for(i = 0; i< scount; i++){
if(NULL == fgets(data[i], (int) sizeof(data), stdin)){
printf("Could not get line\n");
exit(EXIT_FAILURE);
}
}
qsort(data, scount, sizeof(char*), cmpstr);
ofile = fopen(argv[2], "w+");
if(ofile == NULL){
printf("Could not open output file. \n");
}
for(i = 0; i<scount; i++){
fputs(data[i], ofile);
}
fclose(ofile);
for(i=0; i<scount; i++){
if(data[i]) free(data[i]);
}
if (data) free(data);
exit (EXIT_SUCCESS);
return 0;
}
However, when I compiled it it gave me a segmentation fault. I tried using the gdb debugger to try and debug it but it did not give me anything really and I barely understand how to use gdb. But my takeaway from the usage of gdb is that there is not enough memory allocated which confuses me since I allocated memory using malloc.
data = (char **) malloc(scount * sizeof(char*));
Here you allocate memory for an array of pointers. You never initialize the contents of that array. Therefore, when you access data[0] below by passing it to fgets, you're accessing an uninitialized pointer object. If you're lucky, the contents of that uninitialized memory constitutes an invalid address, and when fgets attempts to store data there, your program crashes. If you're unlucky, the contents of that uninitialized memory happens to be the address of some block of memory that's used by some other object and you get memory corruption that's really hard to debug.
You need to initialize the pointers allocated by malloc. Like any other pointer object, depending on what you want to do, you can initialize them to NULL, to a pointer to an existing object, or to the result of calling a function such as malloc. In this program, you need to obtain storage for the lines that you're going to read, therefore you'll need to call malloc on each of the lines. Since you don't know in advance how long a line will be, this is best done at the time you read the line.
It would be a good idea to first set all the elements to NULL, as soon as you've allocated the array of pointers, and later allocate memory for the individual lines. You don't have to, but it's easier then to keep track of which elements of the array have been initialized and which ones haven't. In particular, that lets you call free on all the array elements without having to worry how many you've already reached.
fgets(data[i], (int) sizeof(data), stdin)
Passing sizeof(data) here doesn't make sense. The variable data is a pointer to char*, so sizeof(data) is just the size of a pointer. It isn't the size of the array that the pointer points to: that size isn't known at compile time, it's the argument you pass to malloc. And even that size is not relevant here: the size is the maximum number of lines you can read (multiplied by the size of a pointer to a line's contents), but what fgets needs is the size of the memory that's allocated for the line.
To keep things simple, let's say you have a maximum line length max_line_length.
data = (char **) malloc(scount * sizeof(char*));
if (data == NULL) ... // omitted error checking
for (i = 0; i < scount; i++)
data[i] = NULL;
for (i = 0; i < scount; i++) {
data[i] = malloc(max_line_length+2); // +2 for line break character and null byte to terminate the string
if (data[i] == NULL) ... // omitted error checking
if(NULL == fgets(data[i], max_line_length, stdin)) ... // omitted error checking
...
}
After this you'll run into another issue as described in the comments, in that cmpstr receives pointers to pointers to line contents, not pointers to line contents. This is explained in How to qsort an array of pointers to char in C?
I thought I understood the answer to this question but I don't. I understand the first result but I still don't know how to do the copy correctly. I tried the following code:
// TstStrArr.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <string.h>
#include <malloc.h>
int main()
{
char ** StrPtrArr;
char InpBuf0[] = "TstFld0";
char InpBuf1[] = "TstFld1";
StrPtrArr = (char **)malloc(2 * sizeof(char *));
StrPtrArr[0] = (char *)malloc(10 + 1);
printf("inpbuf=%s sizeof=%2d ", InpBuf0, sizeof(StrPtrArr[0]));
strncpy_s(StrPtrArr[0], sizeof(StrPtrArr[0]), InpBuf0, _TRUNCATE);
printf("strptrarr=%s\n", StrPtrArr[0]);
StrPtrArr[1] = (char *)malloc(10 + 1);
printf("inpbuf=%s sizeof=%2d ", InpBuf1, sizeof(*StrPtrArr[1]));
strncpy_s(*StrPtrArr[1], sizeof(*StrPtrArr[1]), InpBuf1, _TRUNCATE); // error here
printf("*strptrarr=%s\n", StrPtrArr[1]);
free(StrPtrArr[0]);
free(StrPtrArr[1]);
free(StrPtrArr);
return 0;
}
The result I got was:
inpbuf=TstFld0 sizeof= 4 strptrarr=Tst
inpbuf=TstFld1 sizeof= 1
and the following error:
Exception thrown: write access violation.
destination_it was 0xFFFFFFCD.
The result I thought I'd get was either of the following:
inpbuf=TstFld1 sizeof=11 *strptrarr=TstFld1
inpbuf=TstFld1 sizeof= 1 *strptrarr=T
I understand the first copy copied the input buffer to the 4 byte pointer which was incorrect. I thought the second copy would copy the input buffer to the value of the dereferenced pointer of a size of 11 but it didn't. I'm guessing the copy was to the first character of the string in the array. I don't understand memory enough to know the significance of the address 0xFFFFFFCD but I guess it's in read-only memory thus causing the error.
What is the correct way to do the copy?
(I don't think it matters, but I'm using VS 2015 Community Edition Update 3.)
Why
strncpy_s(*StrPtrArr[1], sizeof(*StrPtrArr[1]), InpBuf1, _TRUNCATE);
?
*StrPtrArr[1] should be StrPtrArr[1] because StrPtrArr is of type char** and you need char* here.
and sizeof(*StrPtrArr[1]) - is quite strange....
actually sizeof(StrPtrArr[1]) also cannot provide correct value.
You should remember size of allocated memory and then use it like:
size_t arrSize = 10 + 1;
StrPtrArr[1] = (char *)malloc(arrSize);
. . .
strncpy_s(StrPtrArr[1], arrSize, InpBuf1, _TRUNCATE);
The problem is that you are using sizeof when deciding how many characters to copy. However, you allocated a fixed number of characters which is not known to sizeof operator: sizeof StrPtrArr[0] is equal to the size of char pointer on your system (four bytes, judging from the output), not 10 + 1. Hence, you need to specify that same number again in the call to secure string copy.
It isn't as complicated as people seem to think.
char* array = calloc( n, sizeof(array[0]) ); // allocate array of pointers
// assign a dynamically allocated pointer:
size_t size = strlen(str) + 1;
array[i] = malloc(size);
memcpy(array[i], str, size);
I intentionally used calloc during allocation, since that sets all pointers to NULL. This gives the advantage that you can harmlessly call free() on the pointer, even before it is assigned to point at a string.
This in turn means that you can easily (re)assign a new string to an index at any time, in the following way:
void str_assign (char** dst, const char* src)
{
size_t size = strlen(src) + 1;
free(*dst);
*dst = malloc(size);
if(*dst != NULL)
{
memcpy(*dst, src, size);
}
}
...
str_assign(&array[i], "something");
Considering the toy-code as follows:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STRING_LENGTH (5000)
typedef struct request_body_s {
char *data;
size_t size; // in bytes
} request_body_t;
int do_something(request_body_t *request_body) {
char* content = read_content_elsewhere("/dir/content");
size_t size = strlen(content) * sizeof(char);
request_body->data = (char *) realloc(request_body->data, size + 1);
if (request_body->data == NULL)
return 0;
else {
request_body->size = size;
strncpy(request_body->data, content, MAX_STRING_LENGTH);
return 1;
}
}
int main(int argc, char *argv[]) {
request_body_t request_body;
request_body.data = malloc(1);
request_body.size = 0;
if (do_something(&request_body))
printf("Read!\n");
else {
printf("Error!\n");
exit(0);
}
free(request_body.data);
request_body.size = 0;
}
This code seems work fine until free(request_body.data) is called; it generates an error as follows:
*** free(): invalid next size (fast): 0x0000000001594570 ***
What is (of course) wrong and why? Thanks for any suggestion.
I believe the issue is right here:
strncpy(request_body->data, content, MAX_STRING_LENGTH);
depending on your goal (not clear from your description), I would suggest:
strncpy(request_body->data, content, size > MAX_STRING_LENGTH ? MAX_STRING_LENGTH : size );
strncpy copies the first n chars of the string, that is 5000 in your case. If the source string is smaller that n (5000 here), the rest is padded with zeros, therefore you are accessing further that the end of your bufffer, which leads to undefined behaviour.
You need:
strcpy(request_body->data, content);
It is safe here to use strcpy because we can be sure that the memory allocated by realloc is large enough, because you realloc strlen(content) + 1 chars.
BTW * sizeof(char) is always 1 by definition, so the * sizeof(char) is not necessary.
As written in the strncpy manual,
If the length of src is less than n, strncpy() writes additional null bytes to dest to ensure that a total of n bytes are written.
So, by using strncpy(request_body->data, content, 5000);, you write many '\0' outside your buffer. You shouldn't ever do that, it's an undefined behaviour, and, in this case, you're writing on the 'metadata' used by free, so it crashes.
Here, it would be preferable to use strcpy (and make sure to add a '\0' at the end), or memcpy, because you know the size you want to write.
Also, casting the return of malloc is useless, and sizeof(char) is and will very probably always be 1, so it's also useless.
My code works fine, but I am receiving valgrind errors. I want to know how to correct my code to in respect to using these malloc and free statements correctly with the char * * dest. Please don't tell me not to malloc and free unless I am doing it at the incorrect locations. Having either or both the corrected code for strcat_ex in answer03.c or an explanation of my misunderstanding of malloc, free, and initialization after malloc would be greatly appreciated. I apologize in advance for the long post, but I wanted to provide everything necessary.
More information: I am mainly focusing on the method strcat_ex (this is not the same as strncat -- read the description of the function to see the difference with int *n). The problem occurs with the fact that I need to remalloc the parameter memory for the string (char *) in dest (char **) and if it doesn't have enough space allocated with it and after I malloc it is not initialized. This doesn't make sense to me how to initialize "heap" memory after a malloc. I didn't believe initialization had to happen after a malloc.
Note: pa03.c and answer03.h should not be changed at all.
Here is the relevant valgrind error (memcheck.log):
==28717== 1 errors in context 7 of 10:
==28717== Conditional jump or move depends on uninitialised value(s)
==28717== at 0x402D09C: strcat (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==28717== by 0x8048F98: strcat_ex (answer03.c:29)
==28717== by 0x8048631: main (pa03.c:16)
==28717== Uninitialised value was created by a heap allocation
==28717== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==28717== by 0x8048F46: strcat_ex (answer03.c:21)
==28717== by 0x8048631: main (pa03.c:16)
==28717==
==28717== ERROR SUMMARY: 10 errors from 10 contexts (suppressed: 0 from 0)
Lines referenced:
Line 16 (from pa03.c) SHOULD NOT BE CHANGE. Serves as an example of a call to the method parameters def, n, src, and return variable result are declared below in pa03.c:
result=strcat_ex(&dest, &n, src);
Line 21 (from answer03.c):
char * buffer = malloc(1 + 2 * (sizeOfDest + strlen(src)));
Line 29 (from answer03.c):
buffer = strcat(buffer,src);
Here is the relevant source code. This is where the valgrind error is and stackoverflow knowledge is needed (answer03.c):
Edit: Comments have been added and lines have been commented out to remove an error of mine in the code that had nothing to do directly with my question. I apologize for these heinous mistakes, but left the lines in there to help future readers understand.
#include "answer03.h"
#include <string.h>
char * strcat_ex(char * * dest, int * n, const char * src)
{
//Edit: Removed Line Below - Irrelevant variable resplaced with *n
//int sizeOfDest;
if(*dest == NULL)
{
*n = 0;
}
else
{
//Edit: Removed Line Below - variable replaced with *n
//sizeOfDest = strlen(*dest);
}
//Edit: Removed Line Below
//if(*dest != NULL && sizeOfDest >= 1 + sizeOfDest + strlen(src))
//Edit: Corrected Line
if(*dest !=NULL && *n >= 1 + strlen(*dest) + strlen(src))
{
strcat(*dest, src);
}
else
{
//Edit: *n replaced sizeOfDest and changes needed to be made to reflect this. Commented out lines were incorrect and irrelevant. Lines directly below them are the corrected versions, until you reach the next blank line
//*n = 1 + 2 * (sizeOfDest + strlen(src));
if(*dest != NULL)
*n = 1 + 2 * (strlen(*dest) + strlen(src));
else
*n = 1 + 2 * strlen(src);
//char * buffer = malloc(1 + 2 * (sizeOfDest + strlen(src)));
char * buffer = malloc(sizeof(char) * *n);
if(*dest != NULL)
{
strcpy(buffer, *dest);
free(*dest);
}
*dest = malloc(sizeof(buffer));
buffer = strcat(buffer,src);
*dest = buffer;
}
return *dest;
}
EVERYTHING BELOW THIS POINT SHOULD REMAIN UNCHANGED AND IS KNOWN TO BE CORRECT:
My compile statement (Makefile):
gcc -Wall -Wshadow -g pa03.c answer03.c -o pa03
My valgrind statement (Makefile):
valgrind --tool=memcheck --leak-check=full --verbose --track-origins=yes --log-file=memcheck.log ./pa03
Here is the function definition for strcat_ex (answer03.h):
#ifndef PA03_H
#define PA03_H
#include <stdlib.h>
/**
* Append the C-string 'src' to the end of the C-string '*dest'.
*
* strcat_ex(...) will append the C-string 'src' to the end of the string
* at '*dest'. The parameter 'n' is the address of a int that specifies how
* many characters can safely be stored in '*dest'.
*
* If '*dest' is NULL, or if '*dest' is not large enough to contain the result
* (that is, the sum of the lengths of *dest, src, and the null byte), then
* strcat_ex will:
* (1) malloc a new buffer of size 1 + 2 * (strlen(*dest) + strlen(src))
* (2) set '*n' to the size of the new buffer
* (3) copy '*dest' into the beginning of the new buffer
* (4) free the memory '*dest', and then set '*dest' to point to the new buffer
* (5) concatenate 'src' onto the end of '*dest'.
*
* Always returns *dest.
*
* Why do we need to pass dest as char * *, and n as int *?
* Please see the FAQ for an answer.
*
* Hint: These <string.h> functions will help: strcat, strcpy, strlen.
* Hint: Leak no memory.
*/
char * strcat_ex(char * * dest, int * n, const char * src);
//...
Here is the relevant code that calls source as a test (pa03.c):
#include <stdio.h>
#include <string.h>
#include "answer03.h"
int main(int argc, char **argv)
{
char * src;
char * dest;
char * result;
int n;
src="World!";
dest=NULL;
result=strcat_ex(&dest, &n, src);
printf("src=\"World!\";\ndest=NULL;\nstrcat_ex(&dest, &n, src);\n --> gives %s with n=%d\n",result,n);
result=strcat_ex(&dest, &n, "");
printf("Then strcat_ex(&dest, &n, \"\") yields --> gives %s with n=%d\n",result,n);
strcpy(dest,"abc");
result=strcat_ex(&dest, &n, "def");
printf("Then strcpy(dest,\"abc\"); strcat_ex(&dest, &n, \"def\") yields --> gives %s with n=%d\n",result,n);
free(dest);
//...
Here is the relevant output (print statments from pa03.c):
Note this is correct output (which my current code is able to produce).
src="World!";
dest=NULL;
strcat_ex(&dest, &n, src);
--> gives World! with n=13
Then strcat_ex(&dest, &n, "") yields --> gives World! with n=13
Then strcpy(dest,"abc"); strcat_ex(&dest, &n, "def") yields --> gives abcdef with n=13
//...
Last words:
I have attached the files needed to compile this code as well as the valgrind error log in linux using gcc and valgrind. There is more in the valgrind, but I posted what I believe to be most relevant. Thanks in advance.
Zip including all files:
http://www.filedropper.com/files_11
Your current function is utterly broken. It contains logic that cannot possible see fruition, at least one memory leak, and unchecked concatenation to a target buffer that is uninitialized. Among the things wrong, which are numerous:
Assuming sizeofDest dictates not just the storage of the current target string, but also the capacity of any concatenation operation. This is completely wrong, and is why n is provided to this function.
Outright memory leak: *dest = malloc(sizeof(buffer)); not only allocates a completely incorrect size of memory (the size of a pointer; not what it points to), it summarily leaks said allocation just two lines later.
Dead-code boolean logic: Given any non-negative value N, the expression N >= N + 1 + M, where M is a non-negative value, is impossible to ever be true.
You never use the key piece of information provided in tandem with the target pointer-by-address: the current target buffer size provided by n. That value is critical to this algorithm, as it is what dictates the real size of the target buffer, and in conjunction with the current string length in *dest*, will ultimately dictate whether a resize is required.
This is one way to do this function correctly:
char *strcat_ex(char ** dest, int * n, const char * src)
{
size_t dst_len = 0, src_len = strlen(src);
// determine current string length held in *dest
if (*dest && **dest)
dst_len = strlen(*dest);
size_t req_len = dst_len + src_len + 1;
// is space already available for the concatination?
if (*dest && *n >= req_len)
{
// we already know where the target address of the
// concatination is, and we already know the length of
// what is being copied. just copy chars.
if (src_len)
memcpy(*dest+dst_len, src, src_len+1);
}
else
{
// resize is required
void *tmp = realloc(*dest, req_len);
if (tmp != NULL)
{
// resize worked, original content of *dest retained, so
// we can once again simply copy bytes.
*dest = tmp;
memcpy(*dest+dst_len, src, src_len+1);
*n = (int)req_len;
}
else
{
perror("Failed to resize target buffer");
exit(EXIT_FAILURE);
}
}
return *dest;
}
I take great issue with the author of this design in that they chose int for the variable that holds the magnitude of the destination buffer. That magnitude can clearly never be negative, and it makes no sense to use anything other than the same type used by all standard library size-operations, size_t. I'd bring that to the attention of whoever designed this.
Simple Test
int main()
{
char *dst = NULL;
int n = 0;
strcat_ex(&dst, &n, "some string");
printf("%s : %d\n", dst, n);
strcat_ex(&dst, &n, " more data");
printf("%s : %d\n", dst, n);
*dst = 0; // zero-term the string
strcat_ex(&dst, &n, "after empty");
printf("%s : %d\n", dst, n);
strcat_ex(&dst, &n, " and more");
printf("%s : %d\n", dst, n);
strcat_ex(&dst, &n, " and still more");
printf("%s : %d\n", dst, n);
}
Output
some string : 12
some string more data : 22
after empty : 22
after empty and more : 22
after empty and more and still more : 36
Your Test
Running your test program yields the following:
src="World!";
dest=NULL;
strcat_ex(&dest, &n, src);
--> gives World! with n=7
Then strcat_ex(&dest, &n, "") yields --> gives World! with n=7
Then strcpy(dest,"abc"); strcat_ex(&dest, &n, "def") yields --> gives abcdef with n=7
If *dest is NULL, you will be calling strcat on buffer when buffer has just been malloc'd and it is uninitialized. Either set buffer[0] to 0, or use calloc.
Also, you allocate *dest and then set *dest to buffer two lines later, which leaks some memory. There is no need for the first assignment to *dest.