I implemented a function with variable arguments below:
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define ERR_BUFFER_SIZE 4096
void errPrint(const char *format, ...)
{
va_list argptr;
va_start(argptr, format);
char buf[ERR_BUFFER_SIZE];
vsnprintf(buf, sizeof(buf), format, argptr);
fprintf(stderr, "%s", buf);
va_end(argptr);
}
Then I hope to implement another named "errExit()" based on the function above.
I just tried like below.It works as I hoped but I dont't think it correct.
void errExit(const char *format, ...)
{
errPrint(format);
exit(EXIT_FAILURE);
}
I tried errExit("hello,%s,%s,%s", "arg1","arg2", "arg3"); and it printed "hello,arg1,arg2,arg3" correctly.
But after I added two line code like below, it throwed error Segmentation fault.
void errExit(const char *format, ...)
{
char buf[4096];//added
strcpy(buf, format);//added
errPrint(format);
exit(EXIT_FAILURE);
}
I am very confused.
According to what I learned:
There is only one argument format in the stack of called function errPrint(). I can't believe va_start() will get arguments from the its parent-function errExit().
Since errPrint() works "correctly", why it doesn't work after I add the two lines code? It seems that the code added have no effect.
(My English is not well, hoping you everyone can stand my statement. Thank you! )
In errExit() you need to pass the variable arguments to another function via a va_list argument. As you were not, vsnprintf will eventually crash trying to access non-existing arguments on the stack.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR_BUFFER_SIZE 4096
void verrPrint(const char *format, va_list ap) {
char buf[ERR_BUFFER_SIZE];
vsnprintf(buf, sizeof(buf), format, ap);
fprintf(stderr, "%s", buf);
}
void errPrint(const char *format, ...) {
va_list ap;
va_start(ap, format);
verrPrint(format, ap);
va_end(ap);
}
void errExit(const char *format, ...) {
va_list ap;
va_start(ap, format);
verrPrint(format, ap);
va_end(ap);
exit(EXIT_FAILURE);
}
int main(void) {
errPrint("What is the meaning of %s\n", "life?");
errExit("%d\n", 42);
return 0;
}
Other answers explain what you need to do. This answer is about the asm details of why it happened to work.
In your first errExit, if you look at the compiler-generated assembly for x86-64, all the incoming args would still be in registers when calling errPrint(format);. Even though you don't tell the compiler to pass them, it doesn't do anything that uses registers before calling that other function.
So it happens to work even though you didn't explicitly pass along any args beyond the first (format). It compiles as if you'd written a function that passed along its first 6 args, assuming the x86-64 System V calling convention, and that the args were all integer or pointer (such as char*).
In the C abstract machine, your code is meaningless and has undefined behaviour when errPrint makes a va_list argptr and passes it on to a function (vsnprintf) that references more than 0 elements of that list.
It might help to look at asm for a function that does pass on multiple args:
void foo(char *a, char *b, char *c, char *d, char *f);
int bar(char *a, char *b, char *c, char *d, char *f)
{
foo(a, b, c, d, f);
return 1; // some code after the call, so it can't compile to a tailcall
}
On Godbolt, compiling for Linux with GCC12:
# GCC12 -O3
bar:
# incoming args are in RDI, RSI, RDX, RCX, R8, R9 in that order
# same place a callee will look for them, so passing them on is trivial
sub rsp, 8 # re-align the stack so RSP%16 == 0
call foo
mov eax, 1 # return-value register = 1
add rsp, 8 # restore the stack pointer
ret # pop return address into RIP
Your errExit has equivalent asm before the call, so if there are register args, they get passed on even though you didn't tell the C compiler about that.
errExit:
sub rsp, 8
xor eax, eax # Variadic functions get AL = # of args passed in XMM regs
call errPrint
mov edi, 1
call exit # GCC knows exit() is noreturn
If there was any code before call errPrint, such as a call to strcpy, that would of course step on those arg-passing registers. So they have different values when call errPrint runs, except for the one arg you told the C compiler about. It will save that in a call-preserved register across a call strcpy.
If you'd compiled this for a calling convention with fewer register args, like 32-bit x86 (gcc -m32) where there are only stack args, the pointers that vsnprintf references with %s conversions be from errExit's stack frame, above any args it intended to pass. So probably a stack slot of padding for alignment, then in a debug build maybe a saved EBP, maybe even errExit's own return address.
If you're curious, use %p conversions to print the pointer values instead of trying to dereference and segfaulting.
C doesn't have a way to specify that it should pass on only the register args; if you want to pass on an unknown number of args from a ..., you have to do it with a va_list which works for any number of args, including 7 or more on x86-64 System V. That's a good thing because some targets wouldn't have any register args, so just saving/restoring the register args would pass on 0 args.
I'm showing asm only to understand why one version happened to work, not as a suggestion for anything you can do to write safe and portable C that can compile to asm like this, even if there's no other work before the call errPrint.
Thank you everyone.I have learned your from your answers.
I choose to change my code like below.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR_BUFFER_SIZE 4096
void errPrint(const char *format, va_list arg_list)
{
char buf[ERR_BUFFER_SIZE];
vsnprintf(buf, sizeof(buf), format, arg_list);
fprintf(stderr, "%s", buf);
}
void errExit(const char *format, va_list arg_list)
{
errPrint(format, arg_list);
exit(EXIT_FAILURE);
}
void v_exec(void (*func)(const char *, va_list), const char *format, ...)
{
va_list arg_list;
va_start(arg_list, format);
func(format, arg_list);
va_end(arg_list);
}
int main(void)
{
v_exec(errExit,"%s%d", "hello",520);
return 0;
}
Related
Is it possible to detect at compile time that the fourth argument of vsnprintf() consists of fewer than required by the third argument of vsnprintf()? I mean:
#include <stdio.h>
#include <stdarg.h>
void foo(const char* format, ...)
{
char buffer[256];
va_list args;
va_start (args, format);
vsnprintf(buffer, 256, format, args); // args may points to fewer arguments than expected in the format
va_end (args);
}
int main ()
{
foo("%d %d", 1); // the format requires two arguments, but only one is provided, so a warning from the compiler is expected here
return 0;
}
gcc -Wall main.c compiles the above without warnings
Apply an attribute to foo.
__attribute__((__format__(__printf__, 1, 2)))
void foo(const char* format, ...) {
Compile with -Werror=format.
I'm developping a C project with different kind of users. For logging and debugging I would really appreciate a customized printf function that just adds at the beginning of the messages a label that indacates the type of users. Is it possible without rewriting the printf function from scratch?
Here's a crude example. my_print() is called in the same way as printf(), but it produces some output before processing the arguments. Of course, what that additional output is and where it comes from is something that needs to be determined in a specific application.
The significant fact is that we can provide a printf-like function without reimplementing printf().
#include <stdarg.h>
#include <stdio.h>
void my_printf (const char *fmt,...)
{
va_list ap;
va_start (ap, fmt);
printf ("MY INFO... ");
vprintf (fmt, ap);
va_end (ap);
}
int main (int argc, char **argv)
{
my_printf ("The answer is %d\n", 42);
return 0;
}
I have a standard logging API built into my C code, a nice simple logF(const char *pFormat, ...) thing which has always, so far, been mapped to vprintf(), i.e.:
void logF(const char *pFormat, ...)
{
va_list args;
va_start(args, pFormat);
vprintf(pFormat, args);
va_end(args);
}
The C code above this API must work on multiple embedded platforms. I've just reached a platform (Nordic NRF52840) where the underlying logging interface I have to work with is presented as a variadic macro of the form NRF_LOG_INFO(...).
QUESTION: how do I correctly pass fn(const char *pFormat, ...) into a BLAH(...) macro? My brain hurts....
This is with GCC 4.9.3, though it would be nice to have a solution that is relatively relaxed about C compiler version.
EDIT 1: noted that I could redefine my logF() function to be a variadic macro and map it there, the issue is that I would then have a platform-specific header file rather than a generic one, I would have to move it down into the platform code and have one for each. Not impossible but more messy.
EDIT 2: I was asked for the trail of how NRF_LOG_INFO() expands. Here's the relevant output of the pre-processor:
#define NRF_LOG_INFO(...) NRF_LOG_INTERNAL_INFO( __VA_ARGS__)
#define NRF_LOG_INTERNAL_INFO(...) NRF_LOG_INTERNAL_MODULE(NRF_LOG_SEVERITY_INFO, NRF_LOG_SEVERITY_INFO, __VA_ARGS__)
#define NRF_LOG_INTERNAL_MODULE(level,level_id,...) if (NRF_LOG_ENABLED && (NRF_LOG_LEVEL >= level) && (level <= NRF_LOG_DEFAULT_LEVEL)) { if (NRF_LOG_FILTER >= level) { LOG_INTERNAL(LOG_SEVERITY_MOD_ID(level_id), __VA_ARGS__); } }
#define LOG_INTERNAL(type,...) LOG_INTERNAL_X(NUM_VA_ARGS_LESS_1( __VA_ARGS__), type, __VA_ARGS__)
#define LOG_INTERNAL_X(N,...) CONCAT_2(LOG_INTERNAL_, N) (__VA_ARGS__)
Then depending on number of args, anything up to:
#define LOG_INTERNAL_6(type,str,arg0,arg1,arg2,arg3,arg4,arg5) nrf_log_frontend_std_6(type, str, (uint32_t)(arg0), (uint32_t)(arg1), (uint32_t)(arg2), (uint32_t)(arg3), (uint32_t)(arg4), (uint32_t)(arg5))
void nrf_log_frontend_std_6(uint32_t severity_mid,
char const * const p_str,
uint32_t val0,
uint32_t val1,
uint32_t val2,
uint32_t val3,
uint32_t val4,
uint32_t val5);
It is not possible to pass the arguments from a variadic function to a variadic macro.
As you want to hide the platform specific macro call from the API header you can process the function arguments with vsnprintf instead of vprintf and call the logging macro with format "%s" and the resulting string buffer.
void logF(const char *pFormat, ...)
{
va_list args;
/* Choose a reasonable size or replace with dynamic allocation based on the return value of vsnprintf */
/* This could also be a static variable or a global variable to avoid allocation of a big buffer on the stack. */
char buffer[1024];
va_start(args, pFormat);
vsnprintf(buffer, sizeof(buffer), pFormat, args);
NRF_LOG_INFO("%s", buffer);
va_end(args);
}
Note that you may have to call NRF_LOG_FLUSH before the buffer goes out of scope or gets overwritten. See https://devzone.nordicsemi.com/f/nordic-q-a/22647/nrf_log_info-how-to-print-log-with-string-parameter
I'm looking to write what I would imagine is a fairly common macro. I want to emulate the repeated "-v" options on many POSIX programs by defining a bunch of macros of the following form:
#define V1(str, ...) if(optv >= 1){printf("%s: "str,prog,__VA_ARGS__);}
int main(int argc, char* argv[])
{
// ... stuff ...
int i = 1;
V1("This contains a variable: %d\n",i);
}
// Output:
// ./program: This contains a variable: 1
where optv counts the number of "-v" options found on the command line and prog contains the program name (neither shown). This works well, but the problem is that I have to use a variable. V1("Output") will generate a compiler error. I could always use V1("Output%s","") but there should be a cleaner solution.
The GNU C preprocessor has a special feature that lets you delete the trailing comma when there are no arguments filling the variadic portion by prepending the token-pasting operator ## to __VA_ARGS__:
#define V1(str, ...) if(optv < 1); else printf("%s: "str,prog, ## __VA_ARGS__)
Alternatively, if you wish to remain fully C99 compliant, you could incorporate the the format string parameter into the ellipsis, but in this instance you'll also need to refactor your code since you want to include the extra prog parameter between the format string and the varargs. Something like this might work:
#define V1(...) if(optv < 1); else myprintf(prog, __VA_ARGS__)
int myprintf(const char *prog, const char *fmt, ...)
{
// Print out the program name, then forward the rest onto printf
printf("%s: ", prog);
va_list ap;
va_start(ap, fmt);
int ret = vprintf(fmt, ap);
va_end(ap);
return ret;
}
Then, V1("Output") expands to myprintf(prog, "Output") without using any non-C99 compiler extensions.
EDIT
Also note that I inverted the if condition in the macro, due to some weird issues that can arise if you invoke the macro inside an if statement without braces—see this FAQ for a detailed explanation.
Why don't you use 2 different macros for each verbosity level; one which prints a message and variable, and one which just prints a message?
You should probably write yourself a small support function so that you can do the job cleanly:
extern void vb_print(const char *format, ...);
#define V1(...) do { if (optv >= 1) vb_print(__VA_ARGS__); } while (0)
I assume that both optv and prog are global variables. These would go into a header (you wouldn't write them out in the programs themselves, would you?).
The function can be:
#include <stdio.h>
#include <stdarg.h>
extern const char *prog;
void vb_print(const char *format, ...)
{
va_list args;
va_start(args, format);
printf("%s:", prog);
vprintf(format, args);
va_end(args);
}
There's no rocket science in there. You can tweak the system to your heart's content, allowing a choice of where the information is written, flushing the output, ensuring there's a newline at the end, etc.
Try this:
#define V1X(str, ...) if(optv >= 1) {printf("%s: "str,prog,__VA_ARGS__);} else
#define V1(...) V1X(__VA_ARGS__,0)
I believe that fixes the problem you described, and the else at the end fixed another problem.
Why does the code below gives EXC_BAD_ACCESS, could not access memory?
int combine_strings(char **outputStr,...)
{
va_list ap;
char *s, *out=0;
int len=0;
va_start(ap,outputStr);
while(s=va_arg(ap,char *))
{
len+=strlen(s);
}
va_end(ap);
if(!(out=malloc(len+1)))
exit(1);
*outputStr=out;
va_start(ap,outputStr);
while(s=va_arg(ap,char *))
{
len=strlen(s);
memcpy(out,s,len);
out+=len;
}
va_end(ap);
*out=0;
return 0;
}
I have to disagree with the other previous posters. The original code does not iterate over the same va_list twice. It creates two different ones and iterates over each of them in turn, even though the same variable is used to hold both lists.
In fact, I managed to run the function properly. Hence, my guess is that the problem is in how the function was called. Here is how I called it, note the trailing NULL and the setup of the output parameter:
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
// ... combine_strings() goes here...
int main()
{
char * res;
char * * output = &res;
combine_strings(output, "FOO", "BAR", "BAZ", NULL);
printf("%s\n", *output);
}
The code above outputs FOOBARBAZ as expected.
You cannot iterate over the same va_list twice. You need to create a copy using va_copy().
int combine_strings(char **outputStr,...)
{
va_list ap, ap2;
char *s, *out=0;
int len=0;
va_start(ap,outputStr);
va_copy(ap2, ap);
while(s=va_arg(ap2,char *))
{
len+=strlen(s);
}
va_end(ap2);
if(!(out=malloc(len+1)))
exit(1);
*outputStr=out;
while(s=va_arg(ap,char *))
{
len=strlen(s);
memcpy(out,s,len);
out+=len;
}
va_end(ap);
*out=0;
return 0;
}
Using va_start twice in one function is difficult to get to work on all platforms. See here for more information.
Probably best to use va_copy.
The easy way to answer this kind of question is to run it in a debugger. You'll get a full stack trace, code pointer, and you'll be able to look at the values of all the variables.
To use gdb, first compile the program with debugging symbols (-g in gcc). Then, run it:
gdb program_name
(gdb) run
It will crash and you'll be able to see why.