2014-09-12 5 views
11
#include <stdio.h> 

int puts(const char* str) 
{ 
    return printf("Hiya!\n"); 
} 

int main() 
{ 
    printf("Hello world.\n"); 
    return 0; 
} 

Этот код выводит «Hiya!» при запуске. Может кто-нибудь объяснить, почему?Может ли printf быть заменен автоматически помещается в программу C?

Линия компиляция: gcc main.c

EDIT: это теперь чистый C, и любой посторонний материал был удален из строки компиляции.

+0

Это оптимизация, которая не зависит от варианта оптимизации GCC. – BLUEPIXY

+1

Вопрос помечен как 'c', хотя вызов компилятора предполагает, что код скомпилирован как C++. –

+2

Да, gcc может оптимизировать простую константную строчную печать с 'printf()' to 'puts()', поскольку последняя работает лучше ... поскольку вы заменили стандартную библиотеку puts() локальным символом, вот что вы получить. Если вы используете -O0 вместо -O2, вы должны на самом деле называть 'printf()' – JohnH

ответ

12

Да, компилятор может заменить вызов printf с помощью эквивалент вызова puts.

Поскольку вы определили свою собственную функцию puts с тем же именем, что и стандартная библиотечная функция, поведение вашей программы не определено.

Ссылка: N1570 7.1.3:

Всех идентификаторы с внешней связью в любом из следующих подразделов [это включает puts] всегда зарезервированы для использования в качестве идентификаторов с внешней связью.
...
Если программа объявляет или определяет идентификатор в контексте, в котором оно зарезервировано (за исключением, как это предусмотрено 7.1.4) или определяет идентификатор защищены как макро имя, поведение не определено.

Если вы удалите свою puts функцию и изучить ассемблерный листинг, то может найти вызов puts в сгенерированном коде, где вы назвали printf в исходном коде. (Я видел, что gcc выполняет эту конкретную оптимизацию.)

+2

(Отказ от ответственности: только в размещенных реализациях) – Deduplicator

0

Предположительно, вызовы printf() вашей библиотеки puts().

Ваши посылки() заменяют версию библиотеки.

+6

Фактически, 'printf' is * not * вызывает' puts' at * runtime *, но * компилятор * прав, чтобы заменить 'printf' на' puts' at * compile-time * –

8

Это зависит от компилятора и уровня оптимизации. Самые последние версии GCC, на некоторых общие системах, с некоторыми оптимизациями, способны сделать такую ​​оптимизацию (замену простого printf с puts, которым AFAIU является правовыми нормами аппроксимируемости как C99)

Вы должны включить предупреждения при компиляции (например, попробуйте сначала скомпилировать с gcc -Wall -g, затем отлаживать с gdb, тогда, когда вы уверены в своем коде скомпилировать его с gcc -Wall -O2)

BTW, переопределение puts действительно очень некрасиво, если вы делаете это нарочно (т.е. являются кодирование вашего собственной библиотеки C, а затем вы должны подчиняться стандартам). Вы получаете undefined behavior (см. Также this answer о возможных последствиях UB). На самом деле вам следует избегать переопределения имен, упомянутых в стандарте, если вы действительно не знаете, что вы делаете и что происходит внутри компилятора.

Кроме того, если вы скомпилированы со статической ссылкой, например gcc -Wall -static -O main.c -o yourprog, я буду держать пари, что компоновщик пожаловался бы (о множественном определении puts).

Но IMNSHO ваш код не прав, и вы это знаете.

Кроме того, вы можете скомпилировать, чтобы получить ассемблер, например. с gcc -fverbose-asm -O -S; и вы даже можете попросить gcc пролить лот из файлов «дампа», с gcc -fdump-tree-all -O, который может помочь вам понять, что делает gcc.

Опять же, эта конкретная оптимизация действует и очень полезно : printf рутина любого LIBC должен «интерпретировать» во время выполнения формат печати строки (обработка %s ... и т.д. специально); это на практике довольно медленно. Хороший компилятор прав, избегая вызова printf (и заменяя puts), когда это возможно.

BTW gcc не является единственным компилятором, выполняющим эту оптимизацию. clang также.

Кроме того, если вы компилируете с

gcc -ffreestanding -O2 almo.c -o almo 

программа almo показывает Hello world.


Если вы хотите другой фантазии и surprizing оптимизации, попробуйте скомпилировать

// file bas.c 
#include <stdlib.h> 
int f (int x, int y) { 
    int r; 
    int* p = malloc(2*sizeof(int)); 
    p[0] = x; 
    p[1] = y; 
    r = p[0]+p[1]; 
    free (p); 
    return r; 
} 

с gcc -O2 -fverbose-asm -S bas.c затем просмотрите bas.s; вы не увидите никакого звонка до malloc или до free (фактически, нет call машинная инструкция испускается) и снова gcc: для оптимизации (и так же clang)!

PS: Gnu/Linux/Debian/Sid/x86-64; gcc является версия 4.9.1, clang это версия 3.4.2

+0

Хорошая точка; просто попробовал с gcc, поэтому вопрос стоит, и спасибо за ответ. Проделайте еще несколько исследований и примите ответ позже. :) – Almo

+0

Я знаю, что это неправильно. Мне было интересно, почему это происходит. – Almo

+0

Ничего себе, это довольно круто (фантастическая и неожиданная оптимизация) – Almo

1

Попробуйте ltrace в вашем исполняемом файле. Вы увидите, что printf заменяется на puts вызов компилятором. Это зависит от того, как вы назвали printf

Интересным чтение на это here

+0

@navigaid Спасибо за указание. Я отредактировал ответ. –

Смежные вопросы