Peter Seiderer
November 30, 2005
Take a look at the nm1output when compiled without optimization.
> gcc-3.3.6 -O0 example1.c > nm --undefined-only a.out w __gmon_start__ w _Jv_RegisterClasses U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0As expected the dynamically linked executable a.out will call the function printf. Now we compile the same source code with optimization on.
> gcc-3.3.6 -O1 example1.c > nm --undefined-only a.out w __gmon_start__ w _Jv_RegisterClasses U __libc_start_main@@GLIBC_2.0 U puts@@GLIBC_2.0The nm output indicates that puts is called instead of the original printf from the source code. To verify this we take a look at the intermediate assembler listing. First with optimization off.
> gcc-3.3.6 -O0 -S example1.cHere the resulting example1.s file:
1 .file "example1.c" 2 .section .rodata 3 .LC0: 4 .string "hello world\n" 5 .text 6 .globl main 7 .type main, @function 8 main: 9 pushl %ebp 10 movl %esp, %ebp 11 subl $8, %esp 12 andl $-16, %esp 13 movl $0, %eax 14 subl %eax, %esp 15 movl $.LC0, (%esp) 16 call printf 17 movl $0, %eax 18 leave 19 ret 20 .size main, .-main 21 .ident "GCC: (GNU) 3.3.6"Second with optimization on.
> gcc-3.3.6 -O1 -S example1.cAnd the resulting example1.s file:
1 .file "example1.c" 2 .section .rodata 3 .LC0: 4 .string "hello world" 5 .text 6 .globl main 7 .type main, @function 8 main: 9 pushl %ebp 10 movl %esp, %ebp 11 subl $8, %esp 12 andl $-16, %esp 13 movl $.LC0, (%esp) 14 call puts 15 movl $0, %eax 16 movl %ebp, %esp 17 popl %ebp 18 ret 19 .size main, .-main 20 .ident "GCC: (GNU) 3.3.6"The string constant in line 4 changed from "hello world
w __gmon_start__ w _Jv_RegisterClassesof the nm output.
> gcc-3.4.4 -O1 example2.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0
> gcc-3.4.4 -O1 example3.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0Note that the GCC-4.0.2 is smart enough to optimize this example if an optimization level greater or even
> gcc-4.0.2 -Wall -O2 example3.c nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U puts@@GLIBC_2.0
printf("%s\n", "hello world"); // converted to puts("hello world");
> gcc-3.4.4 -O1 example4.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U puts@@GLIBC_2.0
printf("%c", 'A'); // converted to putchar('A');
> gcc-3.4.4 -O1 example5.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U putchar@@GLIBC_2.0
> gcc-3.4.4 -O1 example6.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0
printf(""); // converted to empty statement
> gcc-3.4.4 -O1 example7.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0
printf("A"); // converted to putchar('A');
> gcc-3.4.4 -O1 example8.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U putchar@@GLIBC_2.0
> gcc-3.4.4 -O1 example9.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0
printf("hello world\n"); // converted to puts("hello world");
> gcc-3.4.4 -O1 example10.c > nm --undefined-only a.out U __libc_start_main@@GLIBC_2.0 U puts@@GLIBC_2.0
> gcc-3.3.6 -Wall -O0 bug_15685.c ./a.out (null)
> gcc-3.3.6 -Wall -O1 bug_15685.c ./a.out Segmentation faultAs stated in the bug report by Andrew Pinski this is not a bug, because both behaviors are allowed by the standard. If the standard states undefined behavior the compiler and/or library is free in what to implement. You should not depend on the one or the other behavior in a portable program.
From the glibc manual: ``If you accidentally pass a null pointer as the argument for a %s conversion, the GNU library prints it as (null). We think this is more useful than crashing. But it's not good practice to pass a null argument intentionally.'' [GLIb].
To get the original call with GCC-4.0.2 you must provide
or
as command line argument.
> gcc-4.0.2 -fno-builtin -O0 example1.c > nm --undefined-only a.out w __gmon_start__ w _Jv_RegisterClasses U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0
> gcc-4.0.2 -fno-builtin-printf -O0 example1.c > nm --undefined-only a.out w __gmon_start__ w _Jv_RegisterClasses U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0
> gcc-3.3.6 -Wall -O0 loop_printf.c > time ./a.out > /dev/null real 0m2.197s user 0m2.186s sys 0m0.010s > gcc-3.3.6 -Wall -O1 loop_printf.c > time ./a.out > /dev/null real 0m1.007s user 0m0.997s sys 0m0.009sIn figure 14 we see that the
4645 /* Expand a call to printf or printf_unlocked with argument list ARGLIST. 4646 Return 0 if a normal call should be emitted rather than transforming 4647 the function inline. If convenient, the result should be placed in 4648 TARGET with mode MODE. UNLOCKED indicates this is a printf_unlocked 4649 call. */ 4650 static rtx 4651 expand_builtin_printf (tree arglist, rtx target, enum machine_mode mode, 4652 bool unlocked) 4653 { 4654 tree fn_putchar = unlocked 4655 ? implicit_built_in_decls[BUILT_IN_PUTCHAR_UNLOCKED] 4656 : implicit_built_in_decls[BUILT_IN_PUTCHAR]; 4657 tree fn_puts = unlocked ? implicit_built_in_decls[BUILT_IN_PUTS_UNLOCKED] 4658 : implicit_built_in_decls[BUILT_IN_PUTS]; 4659 const char *fmt_str; 4660 tree fn, fmt, arg; 4661 4662 /* If the return value is used, don't do the transformation. */ 4663 if (target != const0_rtx) 4664 return 0; 4665 4666 /* Verify the required arguments in the original call. */ 4667 if (! arglist) 4668 return 0; 4669 fmt = TREE_VALUE (arglist); 4670 if (TREE_CODE (TREE_TYPE (fmt)) != POINTER_TYPE) 4671 return 0; 4672 arglist = TREE_CHAIN (arglist); 4673 4674 /* Check whether the format is a literal string constant. */ 4675 fmt_str = c_getstr (fmt); 4676 if (fmt_str == NULL) 4677 return 0; 4678 4679 /* If the format specifier was "%s\n", call __builtin_puts(arg). */ 4680 if (strcmp (fmt_str, "%s\n") == 0) 4681 { 4682 if (! arglist 4683 || TREE_CODE (TREE_TYPE (TREE_VALUE (arglist))) != POINTER_TYPE 4684 || TREE_CHAIN (arglist)) 4685 return 0; 4686 fn = fn_puts; 4687 } 4688 /* If the format specifier was "%c", call __builtin_putchar(arg). */ 4689 else if (strcmp (fmt_str, "%c") == 0) 4690 { 4691 if (! arglist 4692 || TREE_CODE (TREE_TYPE (TREE_VALUE (arglist))) != INTEGER_TYPE 4693 || TREE_CHAIN (arglist)) 4694 return 0; 4695 fn = fn_putchar; 4696 } 4697 else 4698 { 4699 /* We can't handle anything else with % args or %% ... yet. */ 4700 if (strchr (fmt_str, '%')) 4701 return 0; 4702 4703 if (arglist) 4704 return 0; 4705 4706 /* If the format specifier was "", printf does nothing. */ 4707 if (fmt_str[0] == '\0') 4708 return const0_rtx; 4709 /* If the format specifier has length of 1, call putchar. */ 4710 if (fmt_str[1] == '\0') 4711 { 4712 /* Given printf("c"), (where c is any one character,) 4713 convert "c"[0] to an int and pass that to the replacement 4714 function. */ 4715 arg = build_int_2 (fmt_str[0], 0); 4716 arglist = build_tree_list (NULL_TREE, arg); 4717 fn = fn_putchar; 4718 } 4719 else 4720 { 4721 /* If the format specifier was "string\n", call puts("string"). */ 4722 size_t len = strlen (fmt_str); 4723 if (fmt_str[len - 1] == '\n') 4724 { 4725 /* Create a NUL-terminated string that's one char shorter 4726 than the original, stripping off the trailing '\n'. */ 4727 char *newstr = (char *) alloca (len); 4728 memcpy (newstr, fmt_str, len - 1); 4729 newstr[len - 1] = 0; 4730 4731 arg = build_string_literal (len, newstr); 4732 arglist = build_tree_list (NULL_TREE, arg); 4733 fn = fn_puts; 4734 } 4735 else 4736 /* We'd like to arrange to call fputs(string,stdout) here, 4737 but we need stdout and don't have a way to get it yet. */ 4738 return 0; 4739 } 4740 } 4741 4742 if (!fn) 4743 return 0; 4744 return expand_expr (build_function_call_expr (fn, arglist), 4745 target, mode, EXPAND_NORMAL); 4746 }