I have a function A with a body like so
char* A (const char* arg) {
char ret[8192];
B(ret);
return strdup(ret);
}
Function B looks like this (some pseudo code on iteration logic for brevity)
void B(char* ret) {
char retString[8192];
while(ITERATIONS_LEFT) {
snprintf(returnString, 8192, "\n Format %s\n\n", IT_VALUE);
snprintf(returnString, 8192, "\n Val %s\n\n", IT_VALUE_2);
}
strcpy(ret, returnString);
}
So essentially I have a function A that gives another function B a string buffer for B to enter formatted data into. Now this works fine as long as the total data returned from the iterations does not exceed 8196 (just a guess at a 'sufficiently large' value) but I think it would be better if I could do this dynamically and not have to worry about the case where my buffer fills. How would I achieve this in a fairly efficient manner, with the constraints that function A must still call function B, and that B's signature can be changed but A's cannot?
In addition to your allocation problem, you overwrite the same string here:
snprintf(returnString, 8192, "\n Format %s\n\n", IT_VALUE);
snprintf(returnString, 8192, "\n Val %s\n\n", IT_VALUE_2);
You could solve this with a kind of "appender" that re-allocates memory as it is needed by determining the required length by passing a size of 0 to snprintf as Joachim Pileborg suggested:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
struct append_t {
char *str; /* string */
size_t len; /* length of string */
size_t size; /* allocated size */
};
void append(struct append_t *app, const char *fmt, ...)
{
va_list arg;
size_t len;
va_start(arg, fmt);
len = vsnprintf(NULL, 0, fmt, arg);
va_end(arg);
while (app->len + len + 1 >= app->size) {
app->size = app->size ? app->size * 2 : 0x100;
app->str = realloc(app->str, app->size);
// Check and handle error
}
va_start(arg, fmt);
len = vsnprintf(app->str + app->len, app->size - app->len, fmt, arg);
va_end(arg);
app->len += len;
}
int main(int argc, char **argv)
{
struct append_t app = {NULL};
for (int i = 1; i < argc; i++) {
if (i > 1) append(&app, ", ");
append(&app, "'%s'", argv[i]);
}
if (app.str) puts(app.str);
free(app.str);
return 0;
}
Things to note:
The code uses the fact that realloc(NULL, size) behaves like malloc(size). The appender must be initialised to all zero.
vsnprintf is a variant of snprintf that takes a va_list instead of variadic arguments. The v...printf functions allow you to write your own printf-like functions. You can't pass variadic arguments to other functions, you have to create a va_list with the va_... macros from the <stdarg.h> header.
Most compilers can detect mismatches between printing formats and arguments for the standard printf functions. If you wat to benefit from these checks for your function, you could use the appropriate GCC attributes ((format(printf, 2, 3)) or the SAL annotation _Printf_format_string_.
In your example, A would create the appender and pass it to B and then return its .str. You could also return an appender from B and return its .strfrom A.
I suggest the following versions of A and B.
char* A (const char* arg) {
int size = 8192;
char *ret = malloc(size);
B(ret, size);
return ret;
}
void B(char* ret, int size) {
int pos = 0, required;
while(ITERATIONS_LEFT) {
required = snprintf(NULL, 0, "\n Format %s\n\n", IT_VALUE);
if (pos + required >= size) {
size *= 2;
ret = realloc(ret, size);
}
pos += sprintf(ret + pos, "\n Format %s\n\n", IT_VALUE);
required = snprintf(NULL, 0, "\n Val %s\n\n", IT_VALUE_2);
if (pos + required >= size) {
size *= 2;
ret = realloc(ret, size);
}
pos += sprintf(ret + pos, "\n Val %s\n\n", IT_VALUE_2);
}
}
Note that:
buffer size is doubled if it isn't enough. That works well in most cases.
copying is minimized (no strdup or strcpy)
you may want to make a new function with the repeated code in the while loop
In your version of B buffer is overwritten each time you call snprintf. Here the writing position (pos) is updated to append (null terminating char overwritten)
snprintf with NULL argument will return the required buffer size without printing anything anywhere
You may want to check that ret is not NULL after calling realloc
If ret is not NULL it's certain that the buffer is large enough. Thus simple sprintf is used to actually print.
Remember to free the buffer!
I haven't tested the code myself
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 13 days ago.
Improve this question
One of the most painful work to do in C is to figure out the best and most efficient way to work with strings. Been searching and trying things for almost a day now. Anyways, basically, I was trying to create a dynamic string builder function, so here's what I came up with:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
void *alloc(void **ptr, size_t len) {
free(*ptr);
void *mem = malloc(len);
if (mem == NULL) {
fprintf(stderr, "err: alloc failed");
exit(EXIT_FAILURE);
}
return mem;
}
char *str(char **src, const char *fmt, ...) {
if (fmt == NULL) {
return NULL;
}
va_list args;
va_start(args, fmt);
size_t len = vsnprintf(NULL, 0, fmt, args);
if (len < 0) {
return NULL;
}
*src = alloc((void **) src, sizeof(char) * (len + 1));
vsnprintf(*src, len + 1, fmt, args);
va_end(args);
return *src;
}
And here's how I currently use it:
int main(int argc, char *argv[]) {
char *s = str(&s, "FOUND!");
puts(s);
puts(str(&s, "NOT %s", s));
puts(s);
return 0;
}
While it does work, I'm thinking about things like:
Possible memory leaks.
And anything I'm doing wrong with dynamic memory allocation.
As well as, how I initialize a char * with str(&pointer, "value") (if passing uninitiated &pointer is good or bad idea).
Is there anything wrong with the code and possible fix/improvements?
EDIT
Maybe I shouldn't have done this in the first place XD
Fixed according to answers/suggestions:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
void *alloc(void *ptr, size_t len) {
void *mem = realloc(ptr, len);
if (mem == NULL) {
free(ptr);
perror(NULL);
}
return mem;
}
char *str(char *src, const char *fmt, ...) {
if (fmt == NULL) {
return NULL;
}
va_list args1, args2;
va_copy(args2, args1);
va_start(args1, fmt);
int len = vsnprintf(NULL, 0, fmt, args1);
va_end(args1);
if (len < 0) {
return NULL;
}
char *val = alloc(src, len + 1);
va_start(args2, fmt);
int res = vsnprintf(val, len + 1, fmt, args2);
va_end(args2);
free(src);
if (res < 0) {
free(val);
return NULL;
}
return val;
}
int main(int argc, char *argv[]) {
char *s = str(NULL, "FOUND!");
puts(s);
free(s);
s = str(s, "NOT %s", s);
puts(s);
free(s);
return 0;
}
What's changed (and notes (Ns) for future reference):
N: Passing uninitialized &pointer is wrong and is actually an error in disguise.
N: Use realloc instead of malloc which handles NULL initial value and deallocates previous allocated memory.
Changed ** parameters to just *. The initial idea behind ** is to be able to directly modify the *src pointer's value within the function (which somehow works in original code).
Initial declaration changed to char *s = str(NULL, "value"), and updating the value needs to be reassigned s = str(s, "updated: %s", s).
Changed size_t len to int for vsnprintf's return value. size_t is what I've initially used to match malloc's size_t parameter.
N: 2nd vsnprintf call might return different result after the first call, so copied the first va_list using va_copy for the 2nd call.
free old string before returning the actual value.
While it does work
No. It fails in some subtle ways.
Memory free'd too soon
str(&s, "NOT %s", s) performs the free() of s and then attempts to read s: alloc() (with its free()) occurs before 2nd vsnprintf(). This is a fundamental design problem.
Indeterminant value
Code fails with char *s = str(&s, "FOUND!"); as str() eventually attempts free(*ptr); and the value of *ptr is indeterminant.
Missing va_end()
Code should always call va_end() after a va_start().
if (len < 0) {
va_end(args); // add
return NULL;
}
Advanced: malloc(0)
alloc(), as used here, never calls malloc(0). Yet as a general purpose function alloc(..., 0) is possible. In that corner case, malloc(0), returning NULL is not a problem and code should not error out.
Advanced: 2nd vsnprintf() may fail
In multi-thread applications, the 2nd vsnprintf() may return a different value than the first. Code could check the 2nd return value too, but we are getting into some deep issues.
Wrong type
size_t len = vsnprintf(NULL, 0, fmt, args); if (len < 0) is never true. Check the return value of vsnprintf() by saving in a signed type.
Pedantic: vsnprintf() returns an int. In rare implementations, size_t has a narrower range than int. Should you care to cope with such:
//size_t len = vsnprintf(NULL, 0, fmt, args);
//if (len < 0) {
int len = vsnprintf(NULL, 0, fmt, args);
if (len < 0 || len >= SIZE_MAX) {
Improvements:
Compile with all warnings enabled. That will save time.
alloc() is strange to free(*str) and then not update *str.
[Edit] Code needs to free after printing.
OP wants to do str(&s, "NOT %s", s) which obliges s to remain stable throughout the print stage.
Code then must allocate for the new string, do the print and then free the old string.
`alloc()` appears to nearly act like `realloc()`.
Instead, return an error status and use strdup() (or a like function that handles NULL) for initialization.
Alternative untested code:
// Return error flag
bool alloc_alt(void **ptr, size_t len) {
if (len > 0) {
void *mem = realloc(*ptr, len);
if (mem == NULL) {
return true;
// or
fprintf(stderr, "err: alloc(%p, %zu) failed", (void*)ptr, len);
exit(EXIT_FAILURE);
}
*ptr == mem;
} else {
free(*ptr);
*ptr == NULL;
}
return false;
}
With str(), consider updating *src even when fmt == NULL or vsnprintf() fails - maybe free(*src); *src = NULL;?
Your functions alloc() and str() seem fine. You should maybe consider using realloc(), which could give you a small memory use/speed improvement.
You must ensure that you never pass an uninitialized pointer to str(), or your application will crash. Any new string pointer should be zero-initialized before calling the function. Why do you pass a pointer to pointer to the function? You are getting the resulting string back, after all, passing the initial pointer by value would help mitigate the problem, ex:
// expecting a pointer, not a pointer to pointer.
void* alloc(void* p, int len) {
void* result;
result = realloc(p, len);
if (!result) {
free(p);
exit(1);
}
return result;
}
char *str(char *src, const char *fmt, ...) {
char* result;
// figure out len
// ...
result = (char*)alloc(src, len + 1);
// fill the result string
// ...
return result;
}
// now str() can also be called as:
// ...
char* s;
s = str(NULL, "hello %s", "Mike");
// ...
free(s);
You are right to worry about memory leaks. Every single string you create using str() MUST be deallocated using free().
I looking to create a variadic function in C that allows to do something like this:
Send({1,2,3,4,5});
Send({4,5,2});
Send({1,1,1,1,1,1,1,1,1,1,1,1,1});
Note there is no length input and the array is placed inline and without any setup or creation of any variable
Currently i am using formal variadic option such as below (example from here), which is quite convenient but also prone to mistakes which are sometimes hard to debug such as forgetting to place the num_args (still compiles), placing the wrong number of elements etc.
int sum(int num_args, ...) {
int val = 0;
va_list ap;
int i;
va_start(ap, num_args);
for(i = 0; i < num_args; i++) {
val += va_arg(ap, int);
}
va_end(ap);
return val;
}
The typical way to define a function that operates on an arbitrary number of arguments of the same type is to use an array:
int sum(const int array[], size_t n)
{
int sum = 0;
while (n--) sum += array[n];
return sum;
}
That would mean that you would have to create an auxiliary array for each call and invoke the function, perhaps with a countof macro that uses sizeof to determine the size of the array and its first member.
As of C99, you can use compound literals to create such arrays:
int s = sum((int[]){1, 1, 2, 3, 5}, 5);
That might be more convenient and typesafe on the array elements, but still has the danger of getting the count wrong.
You can use a variadic macro to combine compound literals and the countof idiom:
#define SUM(...) sum((int[]){__VA_ARGS__}, \
sizeof((int[]){__VA_ARGS__}) / sizeof(int))
(The compound literal argument of the sizeof will only be evaluated for its size.)
Use it like this:
printf("%d\n", SUM(1, 2, 3, 4, 5));
printf("%d\n", SUM(200.0, 30.0, 5.0));
printf("%d\n", SUM());
(I'm not sure whether such a macro is useful, though. The sum example is contrived at best: sum(a, b, c) can be written as a + b + c. Perhaps a min or max macro for more than two arguments might be useful. In general, I find that I have the data I want in array form already when I work in C.)
If you don't want to pass length, you can use a sentinel value to mark the end. If you don't require full range of int, just use (for example) INT_MIN.
Send(1,1,1,1,1,1,1,1,1,1,1,1,1, INT_MIN);
And use that as end condition in your function's loop.
If you need full range of 32 bits, you could pass 64 bit integers so sentinel value can be outside the range of 32 bits. This will be a bit clunky though, you need to make all the parameters be 64 bit then, probably with suffix ll, and this will also make the code less portable/future proof.
One alternative is to use strings. Depending on your purpose, this can be quite robust, because compiler will warn about invalid format string. Here's the code I tested on gcc, and I belive it also works on clang. I hope it is sufficiently self-explanatory with the comments.
The __attribute__ ((format (printf, 1, 2))) part is quite important, it is the only thing making this robust, because it allows the compiler to check the arguments and produce warnings.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// helper function to get a single string,
// just like vsprintf, except
// returns malloc buffer containing the result text
char *buf_print(const char *fmt, va_list ap) {
int bufsize = 0;
char * buf = NULL;
{
// get the exact size needed for the string,
// ap can only be processed once, a copy is needed
va_list ap2;
va_copy(ap2, ap);
bufsize = vsnprintf(NULL, 0, fmt, ap2);
va_end(ap2);
}
if (bufsize >= 0) {
buf = malloc(bufsize + 1);
if (buf) {
bufsize = vsnprintf(buf, bufsize + 1, fmt, ap);
if (bufsize < 0) {
free(buf);
buf = NULL;
}
}
}
return buf;
}
// get next parseable integer from a string,
// skipping any leading invalid characters,
// and returning pointer for next character, as well as number in *out,
// or returning NULL if nothing could be parsed before end of string
char *parse_next_ll(char *buf, long long *out) {
char *endp = NULL;
while (*buf) {
*out = strtoll(buf, &endp, 10);
if (endp != buf) return endp; // something was parsed successfully
++buf; // nothing was parsed, try again from the next char
};
return NULL; // means *out does not contain valid number
}
// Go through all integers in formatted string, ignoring invalid chars,
// returning -1 on error, otherwise number of valid integers found
int
__attribute__ ((format (printf, 1, 2)))
Send(const char *fmt, ...)
{
int count = -1;
va_list ap;
va_start(ap, fmt);
char * const buf = buf_print(fmt, ap); // needs to be freed, so value must not be lost, so const
va_end(ap);
if (buf) {
count = 0;
long long number = 0;
char *s = buf;
while (s && *s) {
s = parse_next_ll(s, &number);
if (s) {
++count;
printf("Number %d: %lld\n", count, number);
}
}
free(buf);
}
return count;
}
int main()
{
int r = Send("1,2,3,4,%i", 1);
printf("Result: %d\n", r);
return 0;
}
Ouptut of that code:
Number 1: 1
Number 2: 2
Number 3: 3
Number 4: 4
Number 5: 1
Result: 5
I am tring to create a sub-routine that inserts a string into another string. I want to check that the host string is going to have enough capacity to hold all the characters and if not return an error integer. This requires using something like sizeof but that can be called using a pointer. My code is below and I would be very gateful for any help.
#include<stdio.h>
#include<conio.h>
//#include "string.h"
int string_into_string(char* host_string, char* guest_string, int insertion_point);
int main(void) {
char string_one[21] = "Hello mother"; //12 characters
char string_two[21] = "dearest "; //8 characters
int c;
c = string_into_string(string_one, string_two, 6);
printf("Sub-routine string_into_string returned %d and creates the string: %s\n", c, string_one);
getch();
return 0;
}
int string_into_string(char* host_string, char* guest_string, int insertion_point) {
int i, starting_length_of_host_string;
//check host_string is long enough
if(strlen(host_string) + strlen(guest_string) >= sizeof(host_string) + 1) {
//host_string is too short
sprintf(host_string, "String too short(%d)!", sizeof(host_string));
return -1;
}
starting_length_of_host_string = strlen(host_string);
for(i = starting_length_of_host_string; i >= insertion_point; i--) { //make room
host_string[i + strlen(guest_string)] = host_string[i];
}
//i++;
//host_string[i] = '\0';
for(i = 1; i <= strlen(guest_string); i++) { //insert
host_string[i + insertion_point - 1] = guest_string[i - 1];
}
i = strlen(guest_string) + starting_length_of_host_string;
host_string[i] = '\0';
return strlen(host_string);
}
C does not allow you to pass arrays as function arguments, so all arrays of type T[N] decay to pointers of type T*. You must pass the size information manually. However, you can use sizeof at the call site to determine the size of an array:
int string_into_string(char * dst, size_t dstlen, char const * src, size_t srclen, size_t offset, size_t len);
char string_one[21] = "Hello mother";
char string_two[21] = "dearest ";
string_into_string(string_one, sizeof string_one, // gives 21
string_two, strlen(string_two), // gives 8
6, strlen(string_two));
If you are creating dynamic arrays with malloc, you have to store the size information somewhere separately anyway, so this idiom will still fit.
(Beware that sizeof(T[N]) == N * sizeof(T), and I've used the fact that sizeof(char) == 1 to simplify the code.)
This code needs a whole lot more error handling but should do what you need without needing any obscure loops. To speed it up, you could also pass the size of the source string as parameter, so the function does not need to calculate it in runtime.
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
signed int string_into_string (char* dest_buf,
int dest_size,
const char* source_str,
int insert_index)
{
int source_str_size;
char* dest_buf_backup;
if (insert_index >= dest_size) // sanity check of parameters
{
return -1;
}
// save data from the original buffer into temporary backup buffer
dest_buf_backup = malloc (dest_size - insert_index);
memcpy (dest_buf_backup,
&dest_buf[insert_index],
dest_size - insert_index);
source_str_size = strlen(source_str);
// copy new data into the destination buffer
strncpy (&dest_buf[insert_index],
source_str,
source_str_size);
// restore old data at the end
strcpy(&dest_buf[insert_index + source_str_size],
dest_buf_backup);
// delete temporary buffer
free(dest_buf_backup);
}
int main()
{
char string_one[21] = "Hello mother"; //12 characters
char string_two[21] = "dearest "; //8 characters
(void) string_into_string (string_one,
sizeof(string_one),
string_two,
6);
puts(string_one);
return 0;
}
I tried using a macro and changing string_into_string to include the requirement for a size argument, but I still strike out when I call the function from within another function. I tried using the following Macro:
#define STRING_INTO_STRING( a, b, c) (string_into_string2(a, sizeof(a), b, c))
The other function which causes failure is below. This fails because string has already become the pointer and therefore has size 4:
int string_replace(char* string, char* string_remove, char* string_add) {
int start_point;
int c;
start_point = string_find_and_remove(string, string_remove);
if(start_point < 0) {
printf("string not found: %s\n ABORTING!\n", string_remove);
while(1);
}
c = STRING_INTO_STRING(string, string_add, start_point);
return c;
}
Looks like this function will have to proceed at risk. looking at strcat it also proceeds at risk, in that it doesn't check that the string you are appending to is large enough to hold its intended contents (perhaps for the very same reason).
Thanks for everyone's help.
Below is a part of a longer code where malloc'ing for a 2D array is done. Could anyone tell if this is correct? If I introduce static values, the code works fine. Else, seg faults...
enum { LEN = 1024*8 };
char **tab = NULL;
int cur_LEN = LEN;
int count_lineMax = 0;
tab = malloc(count_lineMax * sizeof(*tab));
memset(tab, 0, count_lineMax * sizeof(*tab));
if(tab == NULL && count_lineMax) {
printf("Mem_check\n");
exit(1);
}
for(k=0;k<count_lineMax;k++) {
tab[k] = malloc(cur_LEN*sizeof(*tab[k]));
memset(tab[k], 0, cur_LEN*sizeof(*tab[k]));
if(tab[k] == NULL) {
printf("Mem_check*\n");
exit(1);
}
}
for(l=0;l<count_lineMax;l++) {
free(tab[l]);
}
free(tab);
int count_lineMax = 0;
tab = malloc(count_lineMax * sizeof(*tab));
What is this? You are gonna malloc 0 bytes?
There are at least two ways to build the table while reading lines. One uses the property of realloc() that if its first argument is a null pointer, it will behave like malloc() and allocate the requested space (so the code can be self-starting, using realloc() alone). That code might look like:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { LEN = 1024*8 };
static void error(const char *fmt, ...);
static char *xstrdup(const char *str);
int main(void)
{
char line[LEN];
char **tab = NULL;
int tabsize = 0;
int lineno = 0;
while (fgets(line, sizeof(line), stdin) != 0)
{
if (lineno >= tabsize)
{
size_t newsize = (tabsize + 2) * 2;
char **newtab = realloc(tab, newsize * sizeof(*newtab));
if (newtab == 0)
error("Failed to allocate %zu bytes of memory\n", newsize * sizeof(*newtab));
tab = newtab;
tabsize = newsize;
}
tab[lineno++] = xstrdup(line);
}
/* Process the lines */
for (int i = 0; i < lineno; i++)
printf("%d: %s", i+1, tab[i]);
/* Release the lines */
for (int i = 0; i < lineno; i++)
free(tab[i]);
free(tab);
return(0);
}
static void error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(1);
}
static char *xstrdup(const char *str)
{
size_t len = strlen(str) + 1;
char *copy = malloc(len);
if (copy == 0)
error("Failed to allocate %zu bytes of memory\n", len);
memmove(copy, str, len);
return(copy);
}
The alternative uses malloc() explicitly when the table is empty, and is most simply coded as:
int main(void)
{
char line[LEN];
int tabsize = 4;
int lineno = 0;
char **tab = malloc(tabsize * sizeof(*tab));
if (tab == 0)
error("Failed to allocate %zu bytes of memory\n", tabsize * sizeof(*tab));
...
Everything else can remain untouched.
Note that it can be convenient to have functions xmalloc() and xrealloc() which are guaranteed never to return a null pointer because they report an error instead:
static void *xmalloc(size_t nbytes)
{
void *space = malloc(nbytes);
if (space == 0)
error("Failed to allocate %zu bytes of memory\n", nbytes);
return(space);
}
static void *xrealloc(void *buffer, size_t nbytes)
{
void *space = realloc(buffer, nbytes);
if (space == 0)
error("Failed to reallocate %zu bytes of memory\n", nbytes);
return(space);
}
In the long term (big programs), this cuts down on the total number of times you write 'out of memory' error messages. On the other hand, if you must recover from memory allocation failures (save the user's work, etc), then this is not an appropriate strategy.
The code above creates a ragged array; different entries in tab have different lengths. If you want homogeneous lengths (as in the original code), then you have to replace or modify the xstrdup() function to allocate the maximum length.
You might have noticed that the code in xstrdup() used memmove() instead strcpy(). That's because strlen() already measured how long the string is, so there's no need for the copy code to test each byte to see whether it needs copying. I used memmove() because it can never go wrong, even if the strings overlap, even though in this context it is clear that the strings can never overlap and so memcpy() - which is not guaranteed to work correctly if the strings overlap - could have been used since the strings cannot overlap.
The strategy of allocating (oldsize + 2) * 2 new entries means that the memory reallocation code is exercised often enough during testing, without unduly impacting performance in production. See The Practice of Programming by Kernighan and Pike for a discussion of why this is a good idea.
I almost always use a set of functions similar to the error() function because it dramatically simplifies error reporting. The functions I normally use are part of a package that records and reports the program name (from argv[0]) as well, and that has a fairly wide range of alternative behaviours.
I wrote a variadic C function which mission is to allocate the needed memory for a buffer, and then sprintf the args given to this function in that buffer. But I'm seeing a strange behavior with it. It works only once. If I have two calls for this function it segfaults.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
char *xsprintf(char * fmt, ...)
{
va_list ap;
char *part;
char *buf;
size_t len = strlen(fmt)+1;
va_start(ap, fmt);
while (part = va_arg(ap, char *))
len += strlen(part);
va_end(ap);
buf = (char*) malloc(sizeof(char)*len);
va_start(ap, fmt);
vsprintf(buf, fmt, ap);
va_end(ap);
return buf;
}
int main(int argc, const char *argv[])
{
char *b;
b = xsprintf("my favorite fruits are: %s, %s, and %s", "coffee", "C", "oranges");
printf("size de buf is %d\n", strlen(b)); //this works. After it, it segfaults.
/*
free(b);
b = NULL;
*/
b = xsprintf("my favorite fruits are: %s, %s, and %s", "coffee", "C", "oranges");
printf("size de buf is %d\n", strlen(b));
printf("%s", b);
return 0;
}
here's the output of this program:
size de buf is 46
[1] 4305 segmentation fault ./xsprintftest
Am I doing something wrong? Shouldn't I have used va_start multiple times in a single function? Do you have any alternatives? Thanks a lot! :)
You should be using vsnprintf. Use it twice. Once with a NULL destination/zero size to find out the length of the buffer you need to allocate, then a second time to fill the buffer. That way your function will work even if all the arguments are not strings.
As written, it will fail if there are any non-string arguments (%d, %x, %f, etc.). And counting the number of % characters is not a valid way to get the number of arguments. Your result could be too many (if there are literal % characters encoded as %%) or too few (if arguments are also needed for %*s, %.*d, etc. width/precision specifiers).
Pass NULL as the last arg to xsprintf():
b = xsprintf("my favorite fruits are: %s, %s, and %s",
"coffee", "C", "oranges", (void*)0);
Then your while() loop will see the NULL and terminate properly.
As R.. mentions in the comment below and in another answer, the xsprintf function will fail if there are other format arguments. You are better off using vsprintf as explained in the other answer.
My intent here was simply to demonstrate the use of a sentinel with va_arg.
First off, try using vsnprintf. It's just a good idea.
That's not your problem, though. Your problem is that you can't call va_arg more times than there are arguments. It doesn't return the number of arguments. You must either pass in a parameter telling it how many there are, or extract the number of special tokens in the format string to figure out how many there must implicitly be.
That's the reason why printf can choke your program if you pass it too few arguments; it will just keep pulling things off the stack.
The problem is that in the bit of code where you're accessing the va_arg() list without a particular defined end:
va_start(ap, fmt);
while (part = va_arg(ap, char *))
len += strlen(part);
va_end(ap);
The stdargs.h facilities don't have any built-in method for determining when the end of the va_list() occurs - you need to have that explicitly done by a convention you come up with. Either using a sentinel value (as in bstpierre's answer), or by having a count provided. A count can be an explicit parameter that's provided, or it can be implicit (such as by counting the number of format specifiers in the format string like the printf() family does).
Of course, you also have the issue that your code currently only supports the one kind of format-specifier (%s), but I assumed that that's intentional at this point.
Thanks a lot for your answers and ideas! So I rewrote my function like this:
void fatal(const char *msg)/*{{{*/
{
fprintf(stderr, "program: %s", msg);
abort ();
}/*}}}*/
void *xmalloc(size_t size)/*{{{*/
{
register void *value = malloc(size);
if (value == 0)
fatal ("Virtual memory exhausted");
return value;
}/*}}}*/
void *xrealloc(void *ptr, size_t size)/*{{{*/
{
register void *value = realloc(ptr, size);
if (value == 0)
fatal ("Virtual memory exhausted");
return value;
}/*}}}*/
char *xsprintf(const char *fmt, ...)/*{{{*/
{
/* Heavily inspired from http://perfec.to/vsprintf/pasprintf */
va_list args;
char *buf;
size_t bufsize;
char *newbuf;
size_t nextsize;
int outsize;
int FIRSTSIZE = 20;
bufsize = 0;
for (;;) {
if(bufsize == 0){
buf = (char*) xmalloc(FIRSTSIZE);
bufsize = FIRSTSIZE;
}
else{
newbuf = (char *)xrealloc(buf, nextsize);
buf = newbuf;
bufsize = nextsize;
}
va_start(args, fmt);
outsize = vsnprintf(buf, bufsize, fmt, args);
va_end(args);
if (outsize == -1) {
/* Clear indication that output was truncated, but no
* clear indication of how big buffer needs to be, so
* simply double existing buffer size for next time.
*/
nextsize = bufsize * 2;
} else if (outsize == bufsize) {
/* Output was truncated (since at least the \0 could
* not fit), but no indication of how big the buffer
* needs to be, so just double existing buffer size
* for next time.
*/
nextsize = bufsize * 2;
} else if (outsize > bufsize) {
/* Output was truncated, but we were told exactly how
* big the buffer needs to be next time. Add two chars
* to the returned size. One for the \0, and one to
* prevent ambiguity in the next case below.
*/
nextsize = outsize + 2;
} else if (outsize == bufsize - 1) {
/* This is ambiguous. May mean that the output string
* exactly fits, but on some systems the output string
* may have been trucated. We can't tell.
* Just double the buffer size for next time.
*/
nextsize = bufsize * 2;
} else {
/* Output was not truncated */
break;
}
}
return buf;
}/*}}}*/
And it's working like a charm! Thanks a million times :)