Почему и как GCC компилирует функцию с отсутствующим оператором return? - PullRequest
9 голосов
/ 02 сентября 2011
#include <stdio.h>

char toUpper(char);

int main(void)
{
    char ch, ch2;
    printf("lowercase input : ");
    ch = getchar();
    ch2 = toUpper(ch);
    printf("%c ==> %c\n", ch, ch2);

    return 0;
}

char toUpper(char c)
{
    if(c>='a'&&c<='z')
        c = c - 32;
}

В функции toUpper тип возвращаемого значения - char, но в toUpper () нет возврата.И скомпилируйте исходный код с помощью gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4), fedora-14.

Разумеется, выдается предупреждение: «warning: управление достигает концаvoid function ", но работает хорошо.

Что произошло в этом коде во время компиляции с gcc?Я хочу получить твердый ответ в этом случае.Спасибо:)

Ответы [ 8 ]

19 голосов
/ 02 сентября 2011

Что произошло для вас, так это то, что когда программа на C была скомпилирована на ассемблере, ваша функция toUpper в итоге выглядела примерно так:

_toUpper:
LFB4:
        pushq   %rbp
LCFI3:
        movq    %rsp, %rbp
LCFI4:
        movb    %dil, -4(%rbp)
        cmpb    $96, -4(%rbp)
        jle     L8
        cmpb    $122, -4(%rbp)
        jg      L8
        movzbl  -4(%rbp), %eax
        subl    $32, %eax
        movb    %al, -4(%rbp)
L8:
        leave
        ret

Вычитание 32 было выполнено в регистре% eax. А в соглашении о вызовах x86 это регистр, в котором ожидается возвращаемое значение! Итак ... тебе повезло.

Но, пожалуйста, обратите внимание на предупреждения. Они есть по причине!

7 голосов
/ 02 сентября 2011

Это зависит от прикладного двоичного интерфейса и от того, какие регистры используются для вычислений.

например. на x86 первый параметр функции и возвращаемое значение сохраняются в EAX, поэтому gcc, скорее всего, использует это также для сохранения результата вычисления.

2 голосов
/ 02 сентября 2011

Одна важная вещь, которую необходимо понять, это то, что пропустить оператор возврата редко можно, как диагностируемую ошибку. Рассмотрим эту функцию:

int f(int x)
{
    if (x!=42) return x*x;
}

Пока вы никогда не вызываете его с аргументом 42, программа, содержащая эту функцию, является абсолютно допустимой C и не вызывает никакого неопределенного поведения, несмотря на то, что она будет вызывать UB, если вы вызываете f(42) и впоследствии попытался использовать возвращаемое значение.

Таким образом, хотя компилятор может предоставить эвристику предупреждения для пропущенных операторов возврата, это невозможно сделать без ложных срабатываний или ложных отрицаний. Это является следствием невозможности решения проблемы остановки.

2 голосов
/ 02 сентября 2011

По существу, c вставляется в место, которое позже должно быть заполнено возвращаемым значением; поскольку он не перезаписывается с помощью return, он заканчивается как возвращаемое значение.

Обратите внимание, что полагаться на это (в C или на любом другом языке, где это не является явной языковой функцией, такой как Perl), является плохой идеей ™. В крайности.

1 голос
/ 02 сентября 2011

Я не могу рассказать вам о специфике вашей платформы, поскольку я не знаю ее, но есть общий ответ на поведение, которое вы видите.

Когда компилируется некоторая функция, которая имеет возврат,компилятор будет использовать соглашение о том, как вернуть эти данные.Это может быть машинный регистр или определенная ячейка памяти, например, через стек или что-то еще (хотя обычно используются машинные регистры).Скомпилированный код может также использовать это местоположение (зарегистрироваться или иным образом), выполняя работу функции.

Если функция ничего не возвращает, компилятор не будет генерировать код, который явно заполняет это местоположение возвращаемым значением.Однако, как я сказал выше, он может использовать это местоположение во время функции.Когда вы пишете код, который читает возвращаемое значение (ch2 = toUpper(ch);), компилятор напишет код, который использует свое соглашение о том, как получить этот возврат из обычного местоположения.Что касается кода вызывающей стороны, он будет просто читать это значение из местоположения, даже если там ничего не было написано явно.Следовательно, вы получаете значение.

Теперь посмотрите на пример @ Ray, компилятор использовал регистр EAX, чтобы сохранить результаты операции верхнего регистра.Так получилось, это, вероятно, место, в которое возвращаются значения.На вызывающей стороне ch2 загружается со значением, которое находится в EAX - отсюда и фантомное возвращение.Это относится только к ряду процессоров x86, так как на других архитектурах компилятор может использовать совершенно другую схему при принятии решения о том, как следует организовать соглашение

Однако хорошие компиляторы будут пытаться оптимизировать в соответствии с наборомместные условия, знание кода, правил и эвристики.Поэтому важно отметить, что это просто удача, что это работает.Компилятор может оптимизировать и не делать этого или чего-либо еще - вы не должны отвечать на поведение.

0 голосов
/ 15 июля 2013

Я пробовал небольшую программу:

#include <stdio.h>
int f1() {
}
int main() {
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
}

Результат:

ТЕСТ: <1>

ТЕСТ: <10>

ТЕСТ: <11>

ТЕСТ: <11>

ТЕСТ:<11>

Я использовал компилятор mingw32-gcc, поэтому могут быть различия.

Вы можете просто поиграть и попробовать, например, функцию char.Пока вы не используете значение результата, оно все равно будет работать нормально.

#include <stdio.h>
char f1() {
}
int main() {
    f1();
}

Но я бы порекомендовал установить либо функцию void, либо дать какое-либо возвращаемое значение.

Ваша функция кажетсянужен возврат:

char toUpper(char c)
{
    if(c>='a'&&c<='z')
        c = c - 32;
    return c;
}
0 голосов
/ 08 февраля 2013

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

0 голосов
/ 02 сентября 2011

Локальных переменных нет, поэтому значением в верхней части стека в конце функции будет параметр c. Значение в верхней части стека после выхода является возвращаемым значением. Итак, что бы ни держало c, это возвращаемое значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...