1

Why doesn't the following code work?

#include <stdarg.h>
#include <stdio.h>

// People are missing this in their reponses.... 'fmt' here is passed by
// reference, not by value. So &fmt in _myprintf is the same as &fmt in 
// myprintf2. So va_start should use the address of the fmt char * on the
// stack passed to the original call of myprintf2.
void _myprintf(const char *&fmt, ...)
{
    char buf[2000];
//---
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
//---
    printf("_myprintf:%sn", buf);
}

void myprintf2(const char *fmt, ...)
{
    _myprintf(fmt);
}

void myprintf(const char *fmt, ...)
{
    char buf[2000];
//---
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
//---
    printf(" myprintf:%sn", buf);
}

int main()
{
    const char *s = "string";
    unsigned u = 11;
    char c = 'c';
    float f = 2.22;
    myprintf("s='%s' u=%u c='%c' f=%fn", s, u, c, f);
    myprintf2("s='%s' u=%u c='%c' f=%fn", s, u, c, f);
}

I expected both lines of output to be the same, but they differ:

 myprintf:s='string' u=11 c='c' f=2.220000
_myprintf:s='string' u=2020488703 c='c' f=0.000000

I thought va_start() used the address of the fmt variable, which should be the address of the string pointer on the stack.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Marshall Jobe
  • 89
  • 1
  • 8
  • off topic: Be wary with preceding underscores. They often mean something at the library implementation level. In this case I think you have run afoul of the rules reserving a preceding underscore in the global namespace for implementation use. More here: http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier/228797#228797 – user4581301 Sep 27 '16 at 18:43
  • 1
    Not a C question with invalid C code `_myprintf( const char *&fmt, ... )` Suggest to pick one language. If C, use `void _myprintf(const char *fmt, ...)` – chux - Reinstate Monica Sep 27 '16 at 18:46
  • Names starting with underscore are reserved at file-level. Don't use them. – too honest for this site Sep 27 '16 at 19:13
  • @chux: considering the other functions, it likely is a typo. – too honest for this site Sep 27 '16 at 19:14

3 Answers3

4

va_start does use the address of the variable you give it. With myprintf2, you're only passing one parameter to myprintf, so when you try to access the 2nd parameter (the passed value of s) it isn't there, and you seeing saved registers, the return address, or something else that's sitting on the stack.

To do what you're trying to do, you'll need to pass the va_list variable to a common function called by both of your printf-like functions.

Edit: From the C++ language standard, "If the parameter parmN is of a reference type, or of a type that is not compatible with the type that results when passing an argument for which there is no parameter, the behavior is undefined." (parmN is the parameter passed to va_start.)

Edit 2: Sample uncompiled implementation:

void myprintf_core(const char *fmt, va_list ap);

void myprintf2(const char *fmt, ...) {
    //...
    va_list ap;
    va_start(ap, fmt);
    myprintf_core(fmt, ap);
    va_end(ap);     // could be included in myprintf_core
}

myprintf_core is your _myprintf but without the 3 va_ lines, which have been moved into myprintf2.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • No, I am passing a reference to a pointer, not the pointer by value. So &fmt is the same in myprintf2 and _myprintf. The only reason I had to use the ... in _myprintf, is because the compiler was trying to be too smart and was seeing me use a va_start in a function without '...' and wouldn't let it compile. – Marshall Jobe Sep 27 '16 at 20:20
  • So.... the address of fmt used in _myprintf is the address of the const char * of the original format string on the stack gotten by myprintf2. See.... Also, if you look at the output, the %s and %c arguments are printed correctly. only the the %u and %f ones are not. – Marshall Jobe Sep 27 '16 at 20:22
  • @MarshallJobe You can't pass a reference to va_start; see my edit. – 1201ProgramAlarm Sep 27 '16 at 22:52
  • Just for my benefit. Can you write some code that would work (to 1201ProgramAlarm)? It would make what you are suggesting more clear. – Marshall Jobe Sep 29 '16 at 14:24
1

When calling a function a so-called stack frame is created on the stack, it contains the return address, the arguments and maybe some other meta-data needed by the generated code. The arguments for the current function are not passed on to the new function.

Therefore in myprintf2 when you call _myprintf only the fmt argument is passed, none of the others will be passed. So your vsnprintf call will lead to undefined behavior as it tries to access arguments that doesn't exist.

Semi-graphically the frames on the stack could be seen as something like this:

| .                                      |
| .                                      |
| .                                      |
+----------------------------------------+
| arguments for the _myprintf function   |
| .                                      |
| .                                      |
| .                                      |
| return address                         |
| Stack frame for the _myprintf function |
+----------------------------------------+
| arguments for the myprintf2 function   |
| .                                      |
| .                                      |
| .                                      |
| return address                         |
| Stack frame for the myprintf2 function |
+----------------------------------------+
| arguments for the main function        |
| .                                      |
| .                                      |
| .                                      |
| return address                         |
| Stack frame for the main function      |
+----------------------------------------+
| .                                      |
| .                                      |
| .                                      |

It should make it very easy to see why the arguments to myprintf2 are not available to _myprintf.

The exact format and layout of the stack frames are of course system and compiler dependent.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • I know all about stack frames. You are missing a very important thing. I am passing a reference to the fmt string, not a pointer. va_start uses the address of the fmt string to determine where the arguments following the format string are. main calls myprintf2 and pushes these on the stack 'float, char, unsigned, char *, char * (the format). These all have addresses now. All on the stack. – Marshall Jobe Sep 27 '16 at 20:29
  • The address of fmt tells va_start where the first argument 'char *' is and then the 2nd (the unsigned) and so on. By passing a reference to _myprintf to the fmt string, &fmt in _myprintf is the same as &fmt in myprintf2. – Marshall Jobe Sep 27 '16 at 20:29
  • @MarshallJobe Will not work without hacking and basically reimplementing the `va_` macros though. – Some programmer dude Sep 28 '16 at 05:31
1

See C++14 [support.runtime]/3:

The parameter parmN is the identifier of the rightmost parameter in the variable parameter list of the function definition (the one just before the ...). If the parameter parmN is of a reference type, or of a type that is not compatible with the type that results when passing an argument for which there is no parameter, the behavior is undefined.

So your code causes undefined behaviour because the last parameter before the ... in _myprintf has reference type.

M.M
  • 138,810
  • 21
  • 208
  • 365