Definition of macro printf in epicsStdio.h breaks C++ code

Bug #1786927 reported by Sebastian Marsching
8
This bug affects 1 person
Affects Status Importance Assigned to Milestone
EPICS Base
Status tracked in 7.0
3.15
Fix Released
Low
Ralph Lange
3.16
Fix Released
Low
Unassigned
7.0
Fix Released
Low
Unassigned

Bug Description

In recent EPICS releases (3.16 and 7.0), the following preprocessor macro definition can be found in epicsStdio.h:

#ifdef printf
# undef printf
#endif /* printf */
#define printf epicsStdoutPrintf

This is very problematic for C++ code, because it will break valid code. Consider the following example:

#include <cstdio>
#include <epicsStdio.h>

void foo() {
  std::printf("Hello World\n");
}

This code will not compile because printf is going to be replaced by epicsStdoutPrintf, thus resulting in a function call to std::epicsStdoutPrintf, which obivously does not exist.

So far, the best idea for fixing this problem that I have come up with is not defining this macro when the header is included from C++ code, but maybe someone has a better idea.

Revision history for this message
Andrew Johnson (anj) wrote :

Core Group review at ESS:

In the epicsStdio.h file, conditional on C++, define std::epicsStdoutPrintf() and friends as static inline functions that call ::epicsStdoutPrintf().

Who: TBD.

Revision history for this message
Ralph Lange (ralph-lange) wrote :

Doesn't work.

1) Not nice
printf() is variadic, so the inlined code needs to do the va_list gymnastics and call vfprintf().

2) Show stopper
Inside any C++ code that is "using namespace std;", a call to printf(), i.e. epicsStdoutPrintf(), is ambiguous because of the overloaded epicsStdoutPrintf() and std::epicsStdoutPrintf() functions.

Duh!

Revision history for this message
Ralph Lange (ralph-lange) wrote :

I would like to get 3.15.6-rc1 out of the door tomorrow.
What should I do?
  1. Defer and do nothing
  2. Follow Sebastian's proposal and don't use macros in C++

Maybe...
Can I find out programmatically and in a portable way if <cstdio> has been included?
Then I could only add our functions to std:: if that is the case. (Which would be safe, as even printf() itself would be ambiguous.)

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Firstly, any answer other than "do nothing" should add a macro to skip all of the macro magic of epicsStdio.h. This gives an escape hatch for future problems.

Second. I wasn't thinking properly. The way to handle this is to do exactly what the cstdio header does.

namespace std {
    using ::epicsGetStdin;
    using ::epicsGetStdout;
    using ::epicsGetStderr;
    using ::epicsStdoutPrintf;
    using ::epicsStdoutPuts;
    using ::epicsStdoutPutchar;
}

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> I would like to get 3.15.6-rc1 out of the door tomorrow.

To me, skipping this low priority fix seems reasonable to make this deadline. Having multiple identical 'using' doesn't trigger any warnings or errors w/ gcc 4.8. So Sebastian could add these 'using' definitions in his code now as a temporary fix/backwards compatibility.

Revision history for this message
Ralph Lange (ralph-lange) wrote :

Works fine. Thanks a lot!

Revision history for this message
Ralph Lange (ralph-lange) wrote :
Revision history for this message
Andrew Johnson (anj) wrote :

Ahh, no it doesn't work with the VxWorks 5.5 compiler, both the APS and PSI Jenkins builds for this failed:

/usr/local/vw/tornado22-ppc/host/x86-linux/bin/ccppc -DCPU=PPC604 -DvxWorks=vxWorks -include /usr/local/vw/tornado22-ppc/target/h/vxWorks.h -O2 -Wall -mcpu=604 -mstrict-align -mno-implicit-fp -mlongcall -fno-implicit-templates -fno-builtin -I. -I../O.Common -I. -I../../../src/libCom/osi/compiler/gcc -I../../../src/libCom/osi/compiler/default -I. -I../../../src/libCom/osi/os/vxWorks -I../../../src/libCom/osi/os/posix -I../../../src/libCom/osi/os/default -I.. -I../../../src/libCom/as -I../../../src/libCom/bucketLib -I../../../src/libCom/calc -I../../../src/libCom/cvtFast -I../../../src/libCom/cppStd -I../../../src/libCom/cxxTemplates -I../../../src/libCom/dbmf -I../../../src/libCom/ellLib -I../../../src/libCom/env -I../../../src/libCom/error -I../../../src/libCom/fdmgr -I../../../src/libCom/flex -I../../../src/libCom/freeList -I../../../src/libCom/gpHash -I../../../src/libCom/iocsh -I../../../src/libCom/log -I../../../src/libCom/macLib -I../../../src/libCom/misc -I../../../src/libCom/osi -I../../../src/libCom/pool -I../../../src/libCom/ring -I../../../src/libCom/taskwd -I../../../src/libCom/timer -I../../../src/libCom/yacc -I../../../src/libCom/yacc -I../../../src/libCom/yajl -I../../../include/compiler/gcc -I../../../include/os/vxWorks -I../../../include -I/usr/local/vw/tornado22-ppc/target/h -c ../../../src/libCom/iocsh/iocsh.cpp
In file included from ../../../src/libCom/iocsh/iocsh.cpp:23:
../../../src/libCom/osi/epicsStdio.h:95: `epicsGetStdin' is already declared in this scope
../../../src/libCom/osi/epicsStdio.h:96: `epicsGetStdout' is already declared in this scope
../../../src/libCom/osi/epicsStdio.h:97: `epicsGetStderr' is already declared in this scope
../../../src/libCom/osi/epicsStdio.h:98: `epicsStdoutPrintf' is already declared in this scope
../../../src/libCom/osi/epicsStdio.h:99: `epicsStdoutPuts' is already declared in this scope
../../../src/libCom/osi/epicsStdio.h:100: `epicsStdoutPutchar' is already declared in this scope
../../../configure/RULES_BUILD:239: recipe for target 'iocsh.o' failed

IIRC that compiler didn't actually support namespaces properly, it just pretended to do so.

I'm hoping Dirk will be able to take a look and propose a resolution, probably just disabling the "namespace std { using ::epics*Std*; }" code for gcc versions that old would fix it.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

The problem is that the old g++ does not really have a std namespace, or rather the global namespace is std. Thus defining epicsGetStdin globally is the same as std::epicsGetStdin.

I will try a preprocessor branch to work around this problem which probably only exists in gcc 2..

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

It seems this does the job:

#if !defined(__GNUC__) || (__GNUC__ > 2)

I tested the example foo() as above on vxWorks 5:
> foo
Hello World

Looks fine to me.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Quick test of redirection with this function:

/* foo */
void foo() {
  std::printf("std::printf\n");
  ::printf("::printf\n");
  printf("printf\n");
}
static const iocshFuncDef fooFuncDef =
    {"foo",0,0};
static void fooCallFunc(const iocshArgBuf *)
{
    foo();
}

Linux iocsh:
epics> foo
std::printf
::printf
printf
epics> foo > foo.txt
epics> system "cat foo.txt"
std::printf
::printf
printf
epics>

vxWorks 5.5:
> foo
std::printf
::printf
printf
value = 7 = 0x7
> foo > /home/ioc/foo1.txt
value = 7 = 0x7
> copy < /home/ioc/foo1.txt
std::printf
::printf
printf
value = 0 = 0x0
> iocshCmd "foo"
std::printf
::printf
printf
value = 0 = 0x0
> iocshCmd "foo > /home/ioc/foo2.txt"
value = 0 = 0x0
> copy < /home/ioc/foo2.txt
std::printf
::printf
printf
value = 0 = 0x0
>

All good. But I have not tested on Windows or RTEMS.

Revision history for this message
Sebastian Marsching (sebastian-marsching) wrote :

@Ralph and Dirk: Thanks for your work on this.

Back when I reported the bug, I also implemented a quick fix in the code that was affected by this problem. My fix was to undefine the printf macro so that the code would compile.

Obviously, the solution suggested by Ralph is much better, as it will make the redirects works. The only disadvantage of that fix is that it will not work if "printf" is defined in a different namespace. For example, if a library brought its own version of printf in its own namespace (possibly even using a different signature), a call too libns::printf(...) will still not work.

Unfortunately, I do not have any idea how we can fix this problem while still having redirects when using the "regular" version of printf.

Revision history for this message
Ralph Lange (ralph-lange) wrote :

@Sebastian:

You may have missed the additional #ifndef clause that was introduced in https://git.launchpad.net/epics-base/commit/?id=d35835659cd8ef520904748524a7c4a82d5139f0:

If your code does a

  #define epicsStdioStdPrintfEtc

no printf/puts/putchar replacement will take place, and all printf in whatever namespace will work as expected.

IMHO, supporting libraries bringing their own namespace version of printf with a non-standard signature is a corner case that we shouldn't spend much effort to support.

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.