Безопасно ли передавать «слишком много» аргументов внешней функции? - PullRequest
3 голосов
/ 09 сентября 2010

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

void A(int x, int y){
    //Do stuff
}

Теперь есть и отдельный файл B.c:

extern "C"{
    void A(int x, int y, int z);
}

void B(){
    A(1, 2, 3);
}

Первоначально объявляется, что A имеет только 2 аргумента, но при объявлении в B.c он имеет дополнительный аргумент и вызывается с этим третьим в B (). Я знаю, что такую ​​ситуацию можно создать, например, при соединении с фортрановыми подпрограммами, ИЛИ при динамическом связывании.

Я полагаю, что передавать функцию в дополнительный аргумент небезопасно. Может ли кто-нибудь объяснить, что происходит в памяти при вызове функции и передаче ей аргументов? И, следовательно, насколько безопасно передавать этот «лишний» аргумент, который не используется и не требуется.

Возможно ли, что дополнительный аргумент перезаписывает пробел в памяти, который используется внутри функции? Или вызов функции A выделяет пространство в памяти для аргументов, а затем сообщает A, где находится начало блока памяти аргументов, A считывает первые два аргумента и игнорирует последний, делая его полностью безопасным?

Любая информация о функции будет очень полезной, спасибо.

Ответы [ 7 ]

4 голосов
/ 09 сентября 2010

Связывание определяется реализацией, поэтому точно сказать нельзя.

Тем не менее, другие функции C (особенно vardic-параметры) вынуждают реализацию, которая обычно это позволяет.

Например, я не знаю ни одной реализации, которая потерпела бы неудачу, если бы вы написали:

 printf("%d", 1, 2);

Это, однако, просто напечатало бы "1".

Многие люди здесь поднимают cdecl, pascal и __stdcall соглашения о вызовах. Тем не менее, ни один из них не является частью Стандарта и является признаком определенных реализаций. что возвращает нас к моему первому предложению.

4 голосов
/ 09 сентября 2010

Это зависит от используемого соглашения о вызовах.При cdecl вызывающая сторона помещает аргументы в стек в порядке справа налево, а затем вызываемая сторона обращается к ним, смещая указатель стека.Вызов слишком большого количества аргументов в этом случае ничего не нарушит.

Если, однако, у вас есть соглашение о вызовах слева направо, то все будет сломано.

3 голосов
/ 09 сентября 2010

В соответствии с соглашением о вызовах cdecl вызывающая сторона отвечает за очистку стека, так что это будет безопасно.В отличие от этого, pascal соглашение о вызовах делает вызываемого абонента ответственным за очистку, и это было бы опасно.

1 голос
/ 09 сентября 2010

В C это нарушение ограничений, поэтому оно ведет к неопределенному поведению.

«Если выражение, которое обозначает вызываемую функцию, имеет тип, который включает в себя прототип, количество аргументов должно совпадать с количеством параметров» (C99, §6.5.2.2)

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

0 голосов
/ 09 сентября 2010

По крайней мере, в C и C ++ это не принесет никакого вреда. Аргументы передаются справа налево, и вызываемый абонент отвечает за очистку стека.

Однако компилятор не позволит вам сделать это, если вы не используете переменные параметры или не приводите тип функции. Например:

#include <stdio.h>

static void foo (int a, int b, int c, int d, int e, int f, int g)
{
    printf ("A:%d B:%d C:%d D:%d E:%d F:%d G:%d \n",
            a, b, c, d, e, f, g);
}

int main ()
{
    typedef void (*bad_foo) (int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int);
    foo (1, 2, 3, 4, 5, 6, 7);
    bad_foo f = (bad_foo) (&foo);
    f (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17);
}

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

0 голосов
/ 09 сентября 2010

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

Вот хорошее описание деталей стека: http://www.tenouk.com/Bufferoverflowc/Bufferoverflow2a.html

0 голосов
/ 09 сентября 2010

Такой код нарушает правило One Definition (ну, в любом случае, эквивалент C) ... Работает он или нет, полностью зависит от платформы.

В частности, на x86, если функция была объявлена ​​__cdecl, она будет работать, потому что вызывающая сторона очищает стек, но если она была __stdcall (как большинство функций Win32), вызываемая сторона очищает стек, и в этом случае он будет очищен неправильно (потому что у него слишком много параметров). Поэтому это будет зависеть от используемого соглашения о вызовах этой внешней функции.

Я не могу понять, почему ты когда-нибудь хотел это сделать.

...