Поведение указателя функции C - PullRequest
2 голосов
/ 22 января 2010

Для класса я пишу простую функцию шифрования. Все работает как положено:

int crypt(char *key, int (*callback)(int, char*, int, int))
{
    int c, i = 0, n = strlen(key);
    while((c = fgetc(stdin)) != EOF)
    {
        // only change the char if it's printable
        if(c >= 32 && c <= 126)
            c = callback(c, key, n, i);

        fputc(c, stdout);
        i++;
    }
}

int encryptCallback(int c, char *key, int n, int i)
{
    return (c - 32 + key[i % n]) % 95 + 32;
}
int decryptCallback(int c, char *key, int n, int i)
{
    return (c - 32 - key[i % n] + 3 * 95) % 95 + 32;
}

Используя тестовый набор профессора, все работает. Но когда я изначально написал обратные вызовы, я пропустил возврат. Они были закодированы как:

int encryptCallback(int c, char *key, int n, int i)
{
    c = (c - 32 + key[i % n]) % 95 + 32; // no return
}
int decryptCallback(int c, char *key, int n, int i)
{
   c = (c - 32 - key[i % n] + 3 * 95) % 95 + 32; // no return
}

Когда я запускал код с невозвратными обратными вызовами, вывод был все еще верным, когда я применил тестовый пример (и да, я перекомпилировал, я не работал старый код). Я заметил «ошибку» только когда скомпилировал с -Wall.

Так что я в замешательстве. Почему ccrypt()) получает правильное значение после того, как ему присвоено возвращаемое значение callback (когда обратный вызов ничего не возвращает)? c это не указатель, это просто обычный int.

P.S. Назначение не имеет ничего общего с указателями функций.

Ответы [ 6 ]

7 голосов
/ 22 января 2010

Если не использовать return в функции с типом возвращаемого значения, отличным от void, это неопределенное поведение, если вызывающая сторона пытается использовать возвращаемое значение функции.

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


Если вы компилируете на x86, то основная причина, по которой он сработал, заключается в том, что большинство соглашений о вызовах x86 указывают, что возвращаемое значение типа int возвращается в регистр %eax. В вашем случае компилятор также решил использовать этот регистр для вычисления нового значения c - так что значение, как оказалось, осталось там, чтобы снова появиться как «возвращаемое значение». Если бы вы выполнили еще несколько вычислений в функции после вычисления c, вы бы увидели что-то еще.

1 голос
/ 22 января 2010

Что мне приходит в голову (хотя это может быть глупо), так это то, что c назначается регистру, который используется в качестве регистра "возврата" в вашей архитектуре.

1 голос
/ 22 января 2010

Возможно, это связано с тем, что значения функций возвращаются на вашей платформе. Например, на многих платформах на основе x86 регистр EAX используется для возврата целочисленных значений. Если правильное значение окажется в этом регистре в конце функции (из-за выполняемых вычислений), то вызывающая сторона не почувствует разницы: это так же, как если бы значение было помещено туда намеренно с return заявление.

Это просто (плохо!) Удача, что ваш код действительно сработал с первого раза.

0 голосов
/ 22 января 2010

разберите вашу программу, и вы, вероятно, увидите, что произошло. Принимая одну из ваших функций, единственная разница в c = что-то и c = что-то; Возвращение (с); это добавление строки над popq% rbx. Мой x86 ржавый, но я думаю, что это отвечает. В результате получается, что eax заканчивается результатом, и либо eax, либо -12 в стеке - это место, где возвращаемое значение сохраняется для возврата x86.

imull   $95, %eax, %eax
movl    %ecx, %edx
subl    %eax, %edx
movl    %edx, %eax
addl    $32, %eax
movl    %eax, -12(%rbp)
movl    -12(%rbp), %eax
popq    %rbx
leave
ret
.cfi_endproc
0 голосов
/ 22 января 2010

Архитектура Intel x86 ОСНОВНО архитектура аккумулятора. Это означает, что есть один регистр, который сильно предпочтителен для арифметических и логических результатов. Обычно компиляторы для такой архитектуры машины генерируют код, который оставляет результаты в этом регистре и сохраняет из этого регистра. Соглашения о возврате функций, реализуемые этими компиляторами, обычно указывают возвращаемое значение, которое должно быть в этом регистре, потому что это самое дешевое место для него (поскольку именно там код, который его вычислил, вероятно, оставил его). Все это складывается из «Вам повезло».

0 голосов
/ 22 января 2010

Не определено, что он будет делать, поскольку это должно быть неисправимой ошибкой компиляции. Я хотел бы взглянуть на makefile / ide / compiler и понять, почему он продолжает создавать исполняемый файл, когда у него не должно быть

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