Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 12.01.2014, 15:36   #1
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию Функции - передача параметров

Изучал передачу параметров в функции через стек и остался очень удивленным (может баг компилятора?). Я считал что порядок аргументов в стеке строго регламентирован (__cdecl, __stdcall, ...) и стек растет квантами по 4 байта, т.е. для 5 байтовый массив займет в стеке 8 байт. Следующий код разрушил все мои представления:
Код:
#include <stdio.h>

// Запускал у себя, gcc 4.8.1
void __cdecl fn(char c1, char c2)
{
    printf( "char args: %d\n", int((long long)&c1 - (long long)&c2) );
}

void __cdecl fn2(int c1, int c2)
{
    printf( "int args: %d\n", int((long long)&c1 - (long long)&c2) );
}

int main ()
{
    fn(2,3);
    fn2(2,3);
  
    return 0;
}
// char args: 4
// int args: -4
clang 3.4:
Код:
char args: 1
int args: 4
MSVC:
Код:
char args: -4
int args: -4
Не соблюдается ни порядок аргументов ни одинаковые принципы роста стека под аргументы. В принципе не проблема если все компилировать одним компилятором, но как писать библиотеки?
220Volt вне форума Ответить с цитированием
Старый 12.01.2014, 16:31   #2
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию

Цитата:
Сообщение от 220Volt Посмотреть сообщение
Не соблюдается ни порядок аргументов ни одинаковые принципы роста стека под аргументы. В принципе не проблема если все компилировать одним компилятором, но как писать библиотеки?
Порядок вычисления аргументов функции это - US (unspecified behavior).

Это означает "хер знает, как именно компилятор их там пакует и вычисляет, но проблемы мы не ожидаем".

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

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

К слову: я када то пробовал исследовать входные аргументы на стеке функции компилятором мелкомягких, методом научного тыка установил - в зависимости от различных типов аргументов функции, они могут располагаться по самым разным адресам. И далеко не факт, что это будет непрерывный блок памяти.


Код:
void Foo(int v, int po)
{
    int* p = &v;
    ++p;

    int b = *p; //<--- думаете попали на переменную po? А это далеко не факт.
}



Что бы лазить по стеку существует православный сишный костыль - макросы: va_list, va_start, va_end

Код:
//пример использования
int my_printf(const char * format,...) {
    va_list arg_list;
    int result;
    va_start( arg_list, format );
    result = vprintf(format, arg_list);
    va_end( arg_list );
    return result;
}

Код:
#define debug(format, ...)  printf(stderr, format, __VA_ARGS__) //ISO C 1999.
#define debug(format, args...)  printf(stderr, format, args) //расширение гцц
#define debug(format, ...)  printf(stderr, format, ## __VA_ARGS__)  //расширение гцц

Последний раз редактировалось _Bers; 12.01.2014 в 16:40.
_Bers вне форума Ответить с цитированием
Старый 12.01.2014, 19:03   #3
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Это какая-то магия )). Подключил к приложению написанному на MSVC dll (написанную на gcc), думал будут какие-нибудь косяки с аргументами. Не знаю как, но все правильно связалось.

Последний раз редактировалось 220Volt; 12.01.2014 в 19:08.
220Volt вне форума Ответить с цитированием
Старый 13.01.2014, 21:44   #4
Son Of Pain
Участник клуба
 
Регистрация: 23.12.2010
Сообщений: 1,129
По умолчанию

Цитата:
Сообщение от _Bers Посмотреть сообщение
Порядок вычисления аргументов функции это - US (unspecified behavior).
Дело не в порядке вычисления, а в порядке складывания аргументов в стек. И он как раз четко задан в calling convention. cdecl значит "никаких констант в регистрах, все только в стеке, справа налево".

По теме:
1) Майкрософтовский компилятор сделал все предсказуемо (4 байта разницы, как и должно быть при выравнивании на 4).
2) gcc в первой функции зачем-то сделал так:
Код:
                mov     edx, [ebp+a]
                mov     eax, [ebp+b]
                mov     [ebp+var_C], dl
                mov     [ebp+var_10], al
                lea     edx, [ebp+var_10]
                lea     eax, [ebp+var_C]
                sub     edx, eax
                mov     eax, edx
                mov     [esp+18h+var_14], eax
                mov     [esp+18h+var_18], offset aCharArgsD ; "char args: %d\n"
                call    printf
т.е. сначала положил переданные значения во временные переменные, и потом уже сравнил их адреса. При этом порядок оригиналов в стеке и их выравнивание такие же, как у msvc; потому, очевидно, дллка и прилинковалась без проблем - ей-то без разницы, что там внутри функции происходит, лишь бы cdecl соблюдался.
У гцц много подобных идиосинкразий в кодогенераторе, потому не ищи в этом смысла. Если собрать со включенной оптимизацией - результат станет правильным (-4 -4).

3) Ну а шланга у меня под рукой нет. Подозреваю, что он поступил как и gcc.

p. s. Дизассемблер - очень полезная штука в таких случаях, да.
Son Of Pain вне форума Ответить с цитированием
Старый 14.01.2014, 12:22   #5
220Volt
Форумчанин
 
Регистрация: 14.12.2012
Сообщений: 668
По умолчанию

Спасибо, теперь ясно. Я уже подумал что соглашения о вызовах отменили ))
220Volt вне форума Ответить с цитированием
Старый 14.01.2014, 12:38   #6
Пепел Феникса
Старожил
 
Аватар для Пепел Феникса
 
Регистрация: 28.01.2009
Сообщений: 21,000
По умолчанию

Цитата:
Сообщение от Son Of Pain Посмотреть сообщение
Дело не в порядке вычисления, а в порядке складывания аргументов в стек. И он как раз четко задан в calling convention. cdecl значит "никаких констант в регистрах, все только в стеке, справа налево".
компилятор не обязан вам отдавать адреса аргументов.
он может создать временные переменные для применения аргументов.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
Программа делает то что написал программист, а не то что он хотел.
Функции/утилиты ждут в параметрах то что им надо, а не то что вы хотите.
Пепел Феникса вне форума Ответить с цитированием
Старый 15.01.2014, 02:25   #7
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию

Цитата:
Сообщение от Son Of Pain Посмотреть сообщение
Дело не в порядке вычисления, а в порядке складывания аргументов в стек. И он как раз четко задан в calling convention. cdecl значит "никаких констант в регистрах, все только в стеке, справа налево".
Создайте кортеж с самыми разнообразными типами данных: инты, указатели, классы, и тп.

А потом попытайтесь вытащить эти типы со стека:

Код:
void Foo(std::tuple<int,int*,some, void()>& tuple)
{
     char* ptr =  (char*)&tuple)); //<--- зацепили адрес первого аргумента

     int a1 = *((int*)ptr); //вытащили первый аргумент
   
     //сможете вытащить все остальное?
}
Так, что бы это работало и в релизе тоже. И что бы это было гарантированно валидно.

Я когда то (с++03) изготавливал делегаты.

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

Все факты использования шаблонов прятались за счет вывода типов аргументов из аргументов функций:

Код:
//внешне пользователь даже и не замечает, что имеет дело с шаблонами
Connector connnect(obj, &some::method, a1,a2);

...

connnect(); //запуск эквивалентен: obj.method(a1,a2);
Код:
struct Connector
{

   template<class C, class F, class A1, class A2>
   Connector(C& obj, F method, A1& a1, A2& a2)
   {       //(считайте это псевдокодом)

       //здесь мне известен объект от имени которого будет запуск
       //известен метод, который нужно запустить
       //и аргументы, с которыми нужно сделать запуск

       //я передаю ему указатель на самый первый аргумент в стеке
       //вместо того, что бы передавать все аргументы
       mConnector = new TConnector<C,F,A1,A2>(&obj); 

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

Там под капотом аргументы могут пройти по целой цепочке функций, пока дойдут до окончательного шаблоно-исполнителя.

Вопрос: как это сделать так, что бы по возможности избежать лишних копирований аргументов?


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

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

И это действительно работало. Правда только в дебаг-версии, и то - далеко не всегда. Если все аргументы инты - вроде бы работало.
Стоит подмешать туда какой нибудь указатель, и вместо него при извлечении получался мусор.

Про релиз версию вообще молчу.


Я так и не разобрался до конца с этим вопросом.
Мой тимлид мне порекомендовал не пытаться оптимизировать на спичках, изготавливая мутные и потенциально небезопасные хаки на грани UB

А вместо этого сделать так:

Код:
struct Connector
{

   template<class C, class F, class A1, class A2>
   Connector(C& obj, F method, A1& a1, A2& a2)
   {      
       //не заниматься херней
       mConnector = new TConnector<C,F,A1,A2>(&obj, method,a1,a2); 

   }
};
Впрочем, я изучая этот вопрос, я читал о функциях-элипсах:

void Foo( ... );

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

Поэтому, собственно, я бросил заниматься этой ерундой и больше никогда не пытался сишными хаками долбать стек.
_Bers вне форума Ответить с цитированием
Старый 15.01.2014, 10:12   #8
waleri
Старожил
 
Регистрация: 13.07.2012
Сообщений: 6,330
По умолчанию

Цитата:
Сообщение от 220Volt Посмотреть сообщение
Я уже подумал что соглашения о вызовах отменили
Их никогда и не было - это все извраты отдельных компиляторов.
waleri вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
JQuery: передача параметров функции zizz JavaScript, Ajax 2 31.10.2013 20:10
передача функции в качестве параметров функций, С++ prettynetty Помощь студентам 2 17.03.2012 20:11
Передача параметров _Mixer_ Общие вопросы по Java, Java SE, Kotlin 0 22.09.2011 20:17
Передача параметров в функции(Одинаковые имена дефолта и передаваемого параметра) это плохо? Человек_Борща Общие вопросы Delphi 3 13.04.2011 16:54
Передача параметров функции Кипящий чайник Общие вопросы C/C++ 12 11.08.2010 19:45