1

I'm new to C and have read that each function may only be defined once, but I can't seem to reconcile this with what I'm seeing in the console. For example, I am able to overwrite the definition of printf without an error or warning:

#include <stdio.h>

extern int printf(const char *__restrict__format, ...) {
    putchar('a');
}

int main() {
    printf("Hello, world!");
    return 0;
}

So, I tried looking up the one-definition rule in the standard and found Section 6.9 (5) on page 155, which says (emphasis added):

An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier delared with external linkage is used in an expression [...], somewhere in the entire program there shall be exactly one external definition for that identifier; otherwise, there shall be no more than one.

My understanding of linkage is very shaky, so I'm not sure if this is the relevant clause or what exactly is meant by "entire program". But if I take "entire program" to mean all the stuff in <stdio.h> + my source file, then shouldn't I be prohibited from redefining printf in my source file since it has already been defined earlier in the "entire program" (i.e. in the stdio bit of the program)?


My apologies if this question is a dupe, I couldn't find any existing answers.

user51462
  • 1,658
  • 2
  • 13
  • 41
  • 1
    The trick is, `` does not define the function either. It just provides the function signature to link against and the actual function is defined in the libc shared library. – Locke Nov 19 '22 at 05:19
  • You'll probably find that your compiler knows about `printf()` and maps that to `fputs("Hello, World!", stdout)` (rather than `puts()` since there is no newline at the end of the format string). Consequently, `printf()` isn't called. You can't reliably intercept or replace functions from the Standard C library — the compilers know about those functions. – Jonathan Leffler Nov 19 '22 at 05:20
  • @JonathanLeffler You can use `#undef printf` to override that. – Barmar Nov 19 '22 at 05:20
  • @Locke yes, but then how is my `printf` allowed to shadow the one in libc? Is it because the library implementation is pre-compiled, so the compiler doesn't care since it is only compiling my source file when I do `gcc mysourcefile.c`? – user51462 Nov 19 '22 at 05:32
  • 1
    @Barmar — really? How? It's the compiler mapping `printf()` to `puts()` or `fputs()` — not the preprocessor. That said, I suppose it's possible that the compiler handles that case, but it isn't obvious that undefining `printf()` will cause the function to be called. – Jonathan Leffler Nov 19 '22 at 06:03
  • I've just experimented with GCC 11.2.0, and found that (a) it doesn't use `fputs()` and (b) undefining `printf()` doesn't stop the compiler from calling `puts()`. – Jonathan Leffler Nov 19 '22 at 06:08
  • 2
    @user51462 Your `printf` is not allowed to shadow the one in libc. Attempting to shadow `printf` results in undefined behavior per section 7.1.3/2. Which means the compiler doesn't need to issue a warning or error, and it can choose to use your function, or not, as it pleases. – user3386109 Nov 19 '22 at 06:16
  • @user3386109, ah I see. So basically, by "entire program" in the quote above, they mean only the stuff in the source files actually being *compiled*? And since in my example the "entire program" is just one source file containing only one definition of `printf`, the compiler doesn't complain - is this correct? – user51462 Nov 19 '22 at 06:57
  • 1
    It seems that you have not been properly introduced to [undefined behavior](https://stackoverflow.com/questions/2397984/). – user3386109 Nov 19 '22 at 07:16
  • @user3386109, thank you for the link. I see undefined behaviour is actually defined in the standard (Section 3.4.3) and is different from *unspecified* behaviour (3.4.4), which is something I wasn't aware of. But I'm still confused as to what exactly is going on in my example. So ODR *would* apply but since `printf` is a reserved identifier, redefining it results in undefined behaviour as per 7.1.3(2)? Sorry if I'm way off, it's all still opaque to me :( – user51462 Nov 19 '22 at 08:27
  • 1
    I think you've got it. If the code has a definition for `printf`, then it's violating 7.1.3(2) and it has undefined behavior. The compiler is not required to diagnose the problem, and the executable that the compiler outputs does not have to behave as the programmer might expect. On the other hand, if the code has a definition for `myprintf`, then ODR applies. If there is more than one external definition for `myprintf`, that is a constraint violation per 6.9(3), and the compiler must issue an error message. – user3386109 Nov 19 '22 at 09:30
  • Note the “One Definition Rule” is a C++ rule. C has similar rules, but not something specifically called the “one definition rule.” – Eric Postpischil Nov 19 '22 at 12:39
  • Thank you @user3386109. It seems redefining `myprintf` is a constraint violation only if it is internally linked as per 6.9(3), but undefined behaviour if it is externally linked as per 6.9(5). – user51462 Nov 20 '22 at 23:23

2 Answers2

3

The C standard does not define what happens if there is more than one definition of a function.

… shouldn't I be prohibited…

The C standard has no jurisdiction over what you do. It specifies how C programs are interpreted, not how humans may behave. Although some of its rules are written using “shall,” this is not a command to the programmer about what they may or may not do. It is a rhetorical device for specifying the semantics of C programs. C 2018 4 2 tells us what it actually means:

If a “shall” or “shall not” requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined…

So, when you provide a definition of printf and the standard C library provides a definition of printf, the C standard does not specify what happens. In common practice, several things may happen:

  • The linker uses your printf. The printf in the library is not used.
  • The compiler has built-in knowledge of printf and uses that in spite of your definition of printf.
  • If your printf is in a separate source module, and that module is compiled and inserted into a library, then which printf the program uses depends on the order the libraries are specified to the linker.

While the C standard does not define what happens if there are multiple definitions of a function (or an external symbol in general), linkers commonly do. Ordinarily, when a linker processes a library file, its behavior is:

  • Examine each module in the library. If the module defines a symbol that is referenced by a previously incorporated object module but not yet defined, then include that module in the output the linker is building. If the module does not define any such symbol, do not use it.

Thus, for ordinary functions, the behavior of multiple definitions that appear in library files is defined by the linker, even though it is not defined by the C standard. (There can be complications, though. Suppose a program uses cos and sin, and the linker has already included a module that defines cos when it finds a library module that defines both sin and cos. Because the linker has an unresolved reference to sin, it includes this library module, which brings in a second definition of cos, causing a multiple-definition error.)

Although the linker behavior may be well defined, this still leaves the issue that compilers have built-in knowledge about the standard library functions. Consider this example. Here, I added a second printf, so the program has:

printf("Hello, world!");
printf("Hello, world!\n");

The program output is “aHello, world.\n”. This shows the program used your definition for the first printf call but used the standard behavior for the second printf call. The program behaves as if there are two different printf definitions in the same program.

Looking at the assembly language shows what happens. For the second call, the compiler decided that, since printf("Hello, world!\n"); is printing a string with no conversion specifications and ending with a new-line character, it can use the more-efficient puts routine instead. So the assembly language has call puts for the second printf. The compiler cannot do this for the first printf because it does not end with a new-line character, which puts automatically adds.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Thank you @EricPostpischil, I wasn't aware of [C11 4(2)](https://open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=26). Just to clarify, I had noticed that the redefinition of internally linked identifiers appears in the *Constraints* section in [C11 6.9(3)](https://open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=173), whereas the redefinition of externally linked identifiers appears in the *Semantics* section in C11 6.9(5)... – user51462 Nov 19 '22 at 21:55
  • ...I wasn't sure why this was the case, but after learning about C11 4(2), it seems that violating 6.9(3) *must* result in an error (since 6.9(3) appears in the constraints section), whereas violating 6.9(5) is simply undefined behaviour (since 6.9(5) appears outside the constraints section). So the error I get when I attempt to redefine `extern int foo() {}` is not actually required by the standard. Whereas the error I get when I attempt to redefine `static int foo() {}` *is* required by the standard as it violates constraint 6.9(3). Is this correct? – user51462 Nov 19 '22 at 21:55
  • 1
    @user51462: Yes. And I expect a reason multiple definitions with external linkage are not in a constraints section (so no diagnostic message is required) and are left undefined is that it was a common practice to resolve identifiers by drawing modules from libraries and not complaining by default when there are multiple definitions. At the same time, multiple definitions in direct object modules, rather than libraries, do cause link errors. And the C committee likely did not want to get involved into specifying how links had to be performed and when errors would and would not occur. – Eric Postpischil Nov 20 '22 at 10:46
  • Thank you very much @EricPostpischil, I was stuck on this for some time, so really appreciate your help and detailed answer. – user51462 Nov 20 '22 at 23:20
-1

Please aware of declaration and definition. The term are totally different.

  • stdio.h only provide the declaration. And therefore, when you declare/define in your file, as long as the prototype is similar, it is fine with this.
  • You are free to define in your source file. And if it is available, the final program will link to the yours instead of the one in library.
ThongDT
  • 162
  • 7
  • 1
    It is incorrect that the program will (always) link to the `printf` that a user defines in their own source code rather than using the standard behavior for standard library functions. Compilers have built-in knowledge about the standard library functions and may substitute various optimizations for them, as seen [here](https://godbolt.org/z/d6cTK4s3Y) and explained in my answer. – Eric Postpischil Nov 19 '22 at 12:39
  • Your example is very interesting. – ThongDT Nov 21 '22 at 06:43