1

I have an application that uses a custom putchar(); which until now has been working fine. I bumped up the optimization level of the application to -O2, and now my putchar isn't used. I already use -fno-builtin, and based on some googling I added -fno-builtin-putchar to my CFLAGS, but that didn't matter. Is there a "correct" way to get around this or do I have to go into my code and add something like

#define putchar myputchar

to be able to use -O2 and still pull in my own putchar() function?

edit-- Since my original post of this question, I stumbled on -fno-builtin-functions=putchar, as yet another gcc commandline option. Both this and the one above are accepted by gcc, but don't seem to have any noticeable effect.

edit more-- Experimenting further I see that gcc swallows -fno-builtin-yadayada also, so apparently the options parsing at the gcc front end is just passing the text after the second dash to some lower level which ignores it.

more detail: Three files try1.c, try2.c and makefile...

try1.c:

#include <stdio.h>

int
main(int argc, char *argv[])
{
        putchar('a');
        printf("hello\n");
        return(0);
}

try2.c:

#include <stdio.h>

int
putchar(int c)
{
        printf("PUTCHAR: %c\n",c);
        return(1);
}

makefile:

OPT=

try: try1.o try2.o
        gcc -o try try1.o try2.o

try1.o: try1.c
        gcc -o try1.o $(OPT) -c try1.c

try2.o: try2.c
        gcc -o try2.o $(OPT) -c try2.c

clean:
        rm -f try1.o try2.o try

Here's the output: Notice that without optimization it uses the putchar I provided; but with -O2 it gets it from some other "magic" place...

els:make clean
rm -f try1.o try2.o try
els:make
gcc -o try1.o  -c try1.c
gcc -o try2.o  -c try2.c
gcc -o try try1.o try2.o
els:./try
PUTCHAR: a
hello
els:
els:
els:
els:make clean
rm -f try1.o try2.o try
els:make OPT=-O2
gcc -o try1.o -O2 -c try1.c
gcc -o try2.o -O2 -c try2.c
gcc -o try try1.o try2.o
els:./try
ahello
els:
Ed.
  • 928
  • 1
  • 10
  • 23
  • The problem is in glibc, not in gcc. Try `#include ` then `#undef __USE_EXTERN_INLINES` at the very beginning of your file (that's just a hack to point in the right direction). – Marc Glisse Aug 22 '14 at 15:25
  • Not sure what that is, but I tried it and it didn't matter. I added your two lines just below #include in the above files try1.c and try2.c. – Ed. Aug 22 '14 at 16:59
  • So you're going to have two symbols in your code with the exact same name? Doesn't that normally cause undefined behavior? – Bill Lynch Aug 22 '14 at 17:05
  • It does "seem" like there are two different putchars() somehow; however if that was the case, AFAIK, the linker would/should choke. Plus if there was some conflict with stdio.h, then I should see at least a warning when I compile try2.c, right? – Ed. Aug 22 '14 at 17:15
  • @Ed. Why on earth would you put the 2 lines **below** `stdio.h` when I said to put them **at the very beginning**? The whole point is to have them before `stdio.h`... (which includes `bits/stdio.h` which has an inline version of putchar) – Marc Glisse Aug 22 '14 at 18:30
  • Yea, my mistake there, sorry I'll learn to read someday. :-( I did go back and try your suggestion on a linux PC and it worked. Looking at comments in stdio.h around the __USE_EXTERN_INLINES, things now make sense to me... The -O2 flag in the compiler apparently turns on this flag at build time (I verified this), so that explains why I never noticed this till adding -O2. Thanks! Great discussion here. – Ed. Aug 22 '14 at 19:15

1 Answers1

4

Ideally, you should produce an MCVE (Minimal, Complete, Verifiable Example) or SSCCE (Short, Self-Contained, Correct Example) — two names (and links) for the same basic idea.

When I attempt to reproduce the problem, I created:

#include <stdio.h>

#undef putchar

int putchar(int c)
{
    fprintf(stderr, "%s: 0x%.2X\n", __func__, (unsigned char)c);
    return fputc(c, stdout);
}

int main(void)
{
    int c;
    while ((c = getchar()) != EOF)
        putchar(c);
    return 0;
}

When compiled with GCC 4.9.1 on Mac OS X 10.9.4 under both -O2 and -O3, my putchar function was called:

$ gcc -g -O2 -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Werror pc.c -o pc  
$ ./pc <<< "abc"
putchar: 0x61
putchar: 0x62
putchar: 0x63
putchar: 0x0A
abc
$

The only thing in the code that might be relevant to you is the #undef putchar which removes the macro override for the function.


Why try1.c doesn't call your putchar() function

#include <stdio.h>

int
main(int argc, char *argv[])
{
        putchar('a');
        printf("hello\n");
        return(0);
}

The function putchar() may be overridden by a macro in <stdio.h>. If you wish to be sure to call a function, you must undefine the macro.

If you don't undefine the macro, that will override anything you do. Hence, it is crucial that you write the #undef putchar (the other changes are recommended, but not actually mandatory):

#include <stdio.h>

#undef putchar

int main(void)
{
        putchar('a');
        printf("hello\n");
        return(0);
}

Note that putchar() is a reserved symbol. Although in practice you will get away with using it as a function, you have no grounds for complaint if you manage to find an implementation where it does not work. This applies to all the symbols in the standard C library. Officially, therefore, you should use something like:

#include <stdio.h>

#undef putchar

extern int put_char(int c);     // Should be in a local header
#define putchar(c) put_char(c)  // Should be in the same header

int main(void)
{
        putchar('a');
        printf("hello\n");
        return(0);
}

This allows you to leave your 'using' source code unchanged (apart from including a local header — but you probably already have one to use). You just need to change the implementation to use the correct local name. (I'm not convinced that put_char() is a good choice of name, but I dislike the my_ prefix, for all it is a common convention in answers.)

ISO/IEC 9899:2011 §7.1.4 Use of library functions

Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow: …

Any function declared in a header may be additionally implemented as a function-like macro defined in the header, so if a library function is declared explicitly when its header is included, one of the techniques shown below can be used to ensure the declaration is not affected by such a macro. Any macro definition of a function can be suppressed locally by enclosing the name of the function in parentheses, because the name is then not followed by the left parenthesis that indicates expansion of a macro function name. For the same syntactic reason, it is permitted to take the address of a library function even if it is also defined as a macro.185) The use of #undef to remove any macro definition will also ensure that an actual function is referred to. Any inv ocation of a library function that is implemented as a macro shall expand to code that evaluates each of its arguments exactly once, fully protected by parentheses where necessary, so it is generally safe to use arbitrary expressions as arguments.186) Likewise, those function-like macros described in the following subclauses may be invoked in an expression anywhere a function with a compatible return type could be called.187)


185) This means that an implementation shall provide an actual function for each library function, even if it also provides a macro for that function.

186) Such macros might not contain the sequence points that the corresponding function calls do.

187) Because external identifiers and some macro names beginning with an underscore are reserved, implementations may provide special semantics for such names. For example, the identifier _BUILTIN_abs could be used to indicate generation of in-line code for the abs function. Thus, the appropriate header could specify

#define abs(x) _BUILTIN_abs(x)

for a compiler whose code generator will accept it. In this manner, a user desiring to guarantee that a given library function such as abs will be a genuine function may write

#undef abs

whether the implementation’s header provides a macro implementation of abs or a built-in implementation. The prototype for the function, which precedes and is hidden by any macro definition, is thereby revealed also.

Judging from what you observe, in one set of headers, putchar() is not defined as a macro (it does not have to be, but it may be). And switching compilers/libraries means that now that putchar() is defined as a macro, the missing #undef putchar means that things no longer work as before.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Yep, I agree. Simplify the problem. Done. Check out my updated post. The primary difference here is that the putchar function is in a different file than the places (several in the real code) where it is called. – Ed. Aug 22 '14 at 16:57
  • 1
    Your example `try1.c` is missing the one thing Jonathan Leffler says might be relevant to you. – Ross Ridge Aug 22 '14 at 17:36
  • Yep, I realize that (#undef putchar). Note that this code has been built without that for quite some time (also without the -O2), and has worked just fine using my putchar(). That's similar to my original approach of just redefining putchar to my_putchar. In either case I have to add something to a bunch of files that I'd rather not touch. – Ed. Aug 22 '14 at 17:52
  • When I simplified the problem, as Johnathan suggested, I just built it on my native linux box (Ubuntu) and greatly simplified the CFLAGS. I usually have to put -fno-builtin on the command line to avoid the conflict that I would usually get in try2.c for putting putchar() below #include . I didn't get the conflict. I moved this code to my windows box and built with Cygwin/GCC and I do get the conflict. So I'm more confused... :-( – Ed. Aug 22 '14 at 17:58
  • More... I also notice that building the exact same code using GCC/Cygwin NEVER uses the putchar() in try2.c (with or without the use of -fno-builtin). Seems the confusion here is where is putchar coming from... I know its typically a macro in stdio.h; however if that was the case then I should have gotten a compile-time error when I originally built try2.c right? More confused now... :-( – Ed. Aug 22 '14 at 18:03
  • OK, last comment (my previous comments above were before I saw your updated post above)... I agree with everything you say. I guess my stumbling block was that my original implementation of this (built for a cross-compiled arm/linux system) did not generate any warning/conflict in the file that is the equivalent of the "try2.c" file above, and all calls to putchar() in other files just worked. Thinking about it, that seems wrong, now with -O2 gcc is doing something different (which, in retrospect, seems to be the right thing). Thanks, – Ed. Aug 22 '14 at 18:14
  • Is it then correct to say that the fact that my above example built without error or warning is incorrect behavior for the gcc toolset? In other words, shouldn't "gcc -o try2.o -c try2.c" have generated at least a warning, but more likely a conflict error? – Ed. Aug 22 '14 at 18:26
  • Ordinarily, I would have expected to get some errors from the compiler because it would have been extremely confused by the expansion of the macro `putchar` applied to the function definition. The normal trick to get around that is detailed in the quote from the standard: `int (putchar)(int c) { ... }` for the definition. However, I tried: `int putchar(int c) { return fputc(c, stdout); }` on Linux (an Ubuntu 14.04 derivative with GCC 4.9.1) and it compiled without error (also on Mac OS X 10.9.4 with GCC 4.9.1). I'm not sure, offhand, what magic achieves that result. – Jonathan Leffler Aug 22 '14 at 18:31
  • Ok, thanks for the great discussion... At least my confusion here was somewhat justified. – Ed. Aug 22 '14 at 18:34