продолжаю задавать странные вопросы по шаблонам с переменным числом параметров... давно уже хотел сделать прослойку, которая бы вызывала функции с нестандартным соглашением о вызовах... для начала пытаюсь повторить шаблоном stdcall... пытаюсь сделать как то так: Код (Text): template <typename ... Args> size_t FuncCall(void* FuncAddr, Args ... args) { asm("push %[Param]" : : [Param] "m" (args)...); asm("mov eax, %[FuncAddr]" : : [FuncAddr] "m" (FuncAddr)); asm("call eax"); return 0; }; но компилятор не кушает... подскажите, как поставить эллипсис правильно и вообще возможно ли это... то есть мне надо сгенерировать N пушов для N параметров, и видимо как то развернуть их в обратном порядке... ЗЫ компилятор gcc, но асм-синтаксис интеловский благодаря флагу компиляции, тк меня воротит от at&t синтаксиса... ЗЗЫ можно сделать что-то типа: Код (Text): template <typename ... Args> size_t FuncCall(void* FuncAddr, Args ... args) { size_t Array[sizeof...(args)] = { ((size_t)args)... }; for(uint32_t t = sizeof...(args) - 1; t >= 0; t++) { asm("push %[Param]" : : [Param] "m" (Array[t])); } asm("mov eax, %[FuncAddr]" : : [FuncAddr] "m" (FuncAddr)); asm("call eax"); return 0; }; но это не очень красивое решение, и скорее всего непадающую версию не сделать...
Rel Вообще использование ассемблера (в интеловском ли синтаксисе, или at&t) -- это не очень красивое решение. Попробуй лучше выкинуть ассемблер и воспользоваться функцией __builtin_apply. Я думаю, что агрессивная оптимизация позволит из __builtin_apply сделать то, что надо.
не совсем понял, как использовать __builtin_apply... допустим, я хочу чтобы компилятор вызвал функцию через call eax или через push-ret, как я смогу реализовать это через __builtin_apply... мне важно сделать прослойку, с помощью которой будут сгенерированы нужные мне опкоды... именно строго определенные опкоды, без асма никак не обойтись видимо, да флагам оптимизации я как-то не доверяю в этом вопросе...
Rel Реализовать новую конвенцию вызова для gcc, и __attribute__ к ней? А зачем тогда в качестве основного инструмента используется gcc? Надо взять другой компилятор, оптимизатору которого больше доверия. На самом деле, использование последовательных asm(); asm(); asm();, которые меняют стек и регистры, и надеются при этом, что изменения останутся в силе к моменту следующего asm() -- это совсем неправильно. Компилятор считает регистры и стек чем-то таким, с чем он может творить всё, что ему заблагорассудится. Скажем, увидев: Код (Text): asm("mov eax, %[FuncAddr]" : : [FuncAddr] "m" (FuncAddr)); компилятор имеет полное право на такую логику действий: всё что делает строчка -- изменяет eax, но изменения эти никак не используются, значит всю эту строчку можно "оптимизировать" и просто выкинуть из рассмотрения. gcc так не делает, но если ему по каким-то его загадочным причинам захочется изменить eax сразу после этой строчки, перед call %eax, то gcc это сделает не задумываясь. Тут надо идти другим путём. Надо средствами языка создать на стеке массив, сложить туда все аргументы, положить сверху адрес функции, после чего используя inline-asm загрузить в %esp нужное значение и выполнить ret. Ну или не класть сверху адрес функции, а просто сказать call %eax, настроив предварительно %esp. Кстати с этой настройкой %esp тоже всё не совсем чисто, поскольку компилятор никоим образом не даёт никаких гарантий относительно того, куда и как будет указывать %esp в тот или иной момент работы программы. А это значит, что загрузить в %esp то значение которое нужно для вызова функции можно легко, но будет гораздо сложнее сделать так, чтобы после возврата из функции программа бы не умерла с SIGSEGV. Хотя если использовать -g, при компиляции, то компилятор озаботится тем, чтобы дебуггер смог бы разобраться в работе программы, и тогда он создаст стековый фрейм, и возможно всё будет несколько проще. Но как бы там ни было, провернуть это не дружа с оптимизатором вряд ли удастся. Для этого надо очень хорошо представлять себе, каким образом работает компилятор при разных настройках оптимизации, и какой код он сгенерит в том или ином случае. А, и ещё одно: смена версии компилятора всё равно легко может сделать код нерабочим. Задвинь на шаблоны, напиши функцию типа Код (Text): int funcall(char *fmt, void (*func)(void), ...); fmt вполне может быть чем-то в стиле:"%d%d%p%c", говоря о том, что первые два аргумента -- int'ы, потом указатель и последний -- char. После чего легко можно написать на builtin асме разбор этой fmt, выуживание из стека аргументов, распихивание их по регистрам и стекам согласно конвенции вызова, и последующий вызов функции. Обидно, конечно, что разбор fmt будет происходит в рантайме, но... Но чтож поделаешь. Зато работать будет надёжно, независимо от опций оптимизации и версии компилятора.
вносить изменения в исходники gcc и пересобирать? интересный вариант))) нет я о том, что разные комбинации флагов могут влиять по разному на разных машинах и версиях компилятора... да и надеяться, что флаги компиляции решат данную проблему мне как то не приходится... я вообще говоря думал, что асм-вставки специально не оптимизируются никак... то есть, что написал, то и получил... надо будет уточнить об этом... ну я в принципе сделал асм вставкой с дублированием параметров на стеке в цикле, как указано в ЗЗЫ, но на асме... такое решение работает, но это канеш не так, как я хотел сделать изначально... с текущими флагами оптимизации, установленными для моего проекта асм-вставка никак не оптимизируется...
Rel Не оптимизируются. Это я просто для страха сказал. asm("nop") работает всегда и везде. Но всё же, где гарантии того, что компилятор сохранит значения регистров между двумя вставками? Вы странно рассуждаете. То вы говорите о том, что не хотите ни на что "надеяться", а потом переходите к тому, что "с текущими флагами оптимизации". С другой стороны, конечно же, всё равно этот код будет работать исключительно на x86 и нигде больше, поэтому проблем связанных с переходом на какой-нибудь ARM не возникнет по-определению, точнее количество таких проблем нисколько не увеличиться от того, что код написанный для x86 полагается на особенности работы gcc на этой платформе. Короче, дело конечно ваше, но я бы не стал рассчитывать на сохранность регистров после выхода из блока inline-asm. А, ну да, точно. Я не увидел там вызова alloca, и как-то не сообразил, что gcc может неявно вызывать alloca. ps. На самом деле я вспомнил, что я видел нечто похожее тому, что надо. То есть с одной стороны совсем другое, но в некотором смысле похожее. Есть библиотечка, называется sigc++. В ней есть куча template'ов, которые помогают передавать в C'шные библиотеки функции C++ (в том числе и функции-члены классов). Так вот, там это сделано путём многократного написания кода для всякого разного количества аргументов функции-аргумента. Это конечно же не говорит о том, что иначе сделать нельзя, но всё же, даёт повод задуматься об этом. Хотя, sigc++ работает не только на x86 но также и на x86_64, и вообще везде, где есть соблюдающий стандарты компилятор C++.