Vim (но не исходная Vi!) имеет вкладки, которые я нахожу (во многих контекстах) выше буферов. Можно сказать :tabe [filename]
для открытия файла на новой вкладке. Циклическое повторение между вкладками сделано путем нажатия на вкладку или сочетаниями клавиш [ n] gt
и gT
. Графический Vim даже имеет графические вкладки.
Имейте в виду, что большинство современных архитектур поддерживают защиту от невыполнения для страниц памяти, и операционные системы с некоторым пониманием используют ее в целях безопасности. Это означает, что вы не можете использовать, например, стековую память или произвольную память malloc для хранения этого кода; вам необходимо настроить разрешения, например, с помощью VirtualProtect
в Windows или mprotect
в Unices.
Звучит как работа для нескольких хороших операторов goto и встроенной сборки.
Если вас интересует только C ++, подумайте об использовании «функциональных объектов» - определите класс с одним методом, содержащим логику вашей функции, и оператор перегрузит оператор () в классе, чтобы вызвать метод . Затем, объявив экземпляр вашего класса:
Foo foo;
... вы можете сделать это:
rtn_type result = foo( /* arg list */);
Синтаксически это похоже на сохранение вашей функции в переменной.
Эээ, в данном конкретном случае вы , по сути, используете указатели на функции. Началом буфера, содержащего различные функции , является указатель функции.
Лучше всего записать эту часть в сборку, а затем связать ваш файл C с процедурами сборки на этапе компиляции / компоновки. C просто не поддерживает такого рода работу без серьезного взлома ... сборки.
Почему бы не объявить рассматриваемые функции встроенными ? Это позволит разместить код вашей функции на месте без ущерба для читабельности. (Хотя и с некоторым влиянием, когда дело доходит до отладки на низком уровне скомпилированного кода)
(Как правильно указал Тайлер, компилятор может проигнорировать это - я писал, исходя из предположения, что флаги оптимизации для запуска компилятора для встраивания кода уже использовались).
В зависимости от флагов оптимизации и вашего кода, компилятор может выполнять некоторые другие оптимизации помимо встраивания кода.
Let ' s говорят, что у нас есть (совершенно бессмысленный, fwiw) код:
#include <stdio.h>
static int test = 0;
int inline foo(int a) {
test = 0x1234;
return a + 0x1234;
}
int inline bar(int a) {
test = 0x5678;
return a + 0x5678;
}
int inline baz(int a) {
test = 0x1357;
return a + 0x1357;
}
int main(int argc, char*argv[]) {
int i = getchar();
int j = 0;
switch(i) {
case 'a': j = foo(i);
break;
case 'b': j = bar(i);
break;
case 'c': j = baz(i);
break;
default:
j = 0;
}
printf("j: %d, test: %d\n", j, test);
}
Компилируя его на gcc 4.1.1 с -O2 и выполняя objdump -d для полученного исполняемого файла, мы получаем следующий фрагмент для main ():
08048480 <main>:
8048480: 8d 4c 24 04 lea 0x4(%esp),%ecx
8048484: 83 e4 f0 and $0xfffffff0,%esp
8048487: ff 71 fc pushl -0x4(%ecx)
804848a: 55 push %ebp
804848b: 89 e5 mov %esp,%ebp
804848d: 51 push %ecx
804848e: 83 ec 14 sub $0x14,%esp
8048491: a1 1c a0 04 08 mov 0x804a01c,%eax
8048496: 89 04 24 mov %eax,(%esp)
8048499: e8 b2 fe ff ff call 8048350 <_IO_getc@plt>
804849e: 83 f8 62 cmp $0x62,%eax
80484a1: 74 31 je 80484d4 <main+0x54>
80484a3: 83 f8 63 cmp $0x63,%eax
80484a6: 74 3d je 80484e5 <main+0x65>
80484a8: 31 d2 xor %edx,%edx
80484aa: 83 f8 61 cmp $0x61,%eax
80484ad: 8d 76 00 lea 0x0(%esi),%esi
80484b0: 74 44 je 80484f6 <main+0x76>
80484b2: a1 24 a0 04 08 mov 0x804a024,%eax
80484b7: 89 54 24 04 mov %edx,0x4(%esp)
80484bb: c7 04 24 e8 85 04 08 movl $0x80485e8,(%esp)
80484c2: 89 44 24 08 mov %eax,0x8(%esp)
80484c6: e8 95 fe ff ff call 8048360 <printf@plt>
80484cb: 83 c4 14 add $0x14,%esp
80484ce: 59 pop %ecx
80484cf: 5d pop %ebp
80484d0: 8d 61 fc lea -0x4(%ecx),%esp
80484d3: c3 ret
80484d4: b8 78 56 00 00 mov $0x5678,%eax
80484d9: ba da 56 00 00 mov $0x56da,%edx
80484de: a3 24 a0 04 08 mov %eax,0x804a024
80484e3: eb cd jmp 80484b2 <main+0x32>
80484e5: b8 57 13 00 00 mov $0x1357,%eax
80484ea: ba ba 13 00 00 mov $0x13ba,%edx
80484ef: a3 24 a0 04 08 mov %eax,0x804a024
80484f4: eb bc jmp 80484b2 <main+0x32>
80484f6: b8 34 12 00 00 mov $0x1234,%eax
80484fb: ba 95 12 00 00 mov $0x1295,%edx
8048500: a3 24 a0 04 08 mov %eax,0x804a024
8048505: eb ab jmp 80484b2 <main+0x32>
8048507: 90 nop
Итак, компилятор уже вставил «необходимые goto () s». В качестве побочного эффекта в этом случае он также уже произвел математику добавления 0x1234 / 0x5678 / 0x1357 по мере необходимости - так что результаты - простые ходы.
Чтобы действительно получить машинный код для ваших функций в автоматическом режиме во время процесса сборки, я предлагаю поместить их в отдельный файл .c, а затем скомпилировать его в независимый от позиции код в объектный файл:
gcc -Wall -c -fPIC functions.c -o functions.o
Затем используйте objdump, чтобы выгрузить разборку:
objdump -d functions.o
Выбросьте все, кроме шестнадцатеричных кодов, поместите их в структуру C с помощью xdd -i.
Обратите внимание, что у вас будут довольно серьезные ограничения в этом коде, если вы не хотите иметь дело с перемещением - нет доступа к переменным со статической продолжительностью хранения, нет доступа к внешним функциям. Лучше всего проверить с помощью objdump -r, чтобы убедиться, что перемещение не требуется.
Поскольку функции на самом деле являются просто ярлыками, вам нужен способ определить размер функции. Одна из уловок может заключаться в том, чтобы взять адрес следующей функции. Вот небольшой пример, в котором этот трюк используется для преобразования байт-кода функции «умножения» в вектор символов, затем код переписывается так, чтобы всегда возвращалась константа, а затем вызывается код, преобразовывая его в соответствующий указатель функции.
Это грязный прием и, вероятно, ломается при определенных обстоятельствах / компиляторах. Однако, если вы находитесь в контролируемой среде и это работает на вас, это может помочь вам.
#include <iostream>
#include <vector>
using namespace std;
int multiply( int arg )
{
return arg * 2;
}
int main()
{
// Show that multiply apparently multiplies the given value by two.
cout << multiply( 13 ) << endl;
// Copy the bytes between &multiply and &main (which is hopefully the multiply code) into a vector
vector<char> code( (char*)&multiply, (char*)&main );
// Optimize it by making multiply always return 26
code[0] = 0xB8; // mov eax, 1Ah
code[1] = 0x1A;
code[2] = 0x00;
code[3] = 0x00;
code[4] = 0x00;
code[5] = 0xC3; // ret
// Call the faster code, prints 26 even though '3' is given
cout << ((int (*)(int))&code[0])( 3 ) << endl;
}
Если вы собираетесь компилировать и оптимизировать код во время выполнения, я бы рекомендовал взглянуть на LLVM . ffcall также является полезным пакетом. Обратите внимание, что оба из них решают такие проблемы, как обеспечение того, чтобы память, выделенная для функции, была исполняемой, и что кеш инструкций очищен должным образом для большого количества архитектур. Вы же не хотите изобретать велосипед.
Я не уверен, почему вы исключаете указатели на функции - если вы не изменяете код во время выполнения, я не могу придумать ничего, что мог бы сделать буфер, содержащий функцию указатель на функцию не может.
При этом, если у вас есть указатель на буфер, содержащий вашу функцию, просто приведите указатель на этот буфер к нужному типу указателя функции и вызовите его. Конечно, если ваша операционная система / процессор поддерживает это, вам также придется сбросить флаг NX, который не позволяет запускать неисполняемый код.
Кроме того, вы не можете предполагать, что вы можете копировать буферы, содержащие исполняемые данные, вокруг как если бы они содержали обычные данные - вам придется исправить переходы, переходы и другие обращения к памяти.
РЕДАКТИРОВАТЬ: Я понимаю, к чему вы клоните, но не думаю, что вы сможете найти совместимый со стандартами способ поместить функцию в буфер - C ++ очень мало говорит о структуре памяти, а место хранения функций довольно абстрактное понятие. Вы можете застрять с кучей хаков или использовать отдельные файлы сборки, которые могут быть собраны во время компиляции и загружены из файлов во время выполнения.
С другой стороны, я думаю, вы можете обнаружить, что это быстрее выполнить двойное разыменование при хранении указателя функции (вероятно, 4-8 байтов), чем страдать от промахов кеша при хранении буфера, способного хранить самую большую функцию, которая вам понадобится. Ваша структура с присоединенной функцией имеет много общего с виртуальными функциями в классах. Есть причины, по которым большинство (все?) Компиляторы C ++ реализуют виртуальные функции с помощью vtable, а не хранят полное определение функции для каждого экземпляра класса. Это не означает, что вы не получите увеличения производительности за счет сохранения всей функции в буфере, это просто не так просто, как может показаться.
Я думаю, вы можете оптимизировать что-то немного глупое. Кажется, вы надеетесь избежать дополнительных инструкций, разыменования функции или других подобных глупостей. Позвольте мне порекомендовать альтернативу. Сначала объявите свои функции как обычные, но пометьте их как inline
: встроенный спецификатор указывает компилятору разместить тело функции при вызове функции, а не вызывать функцию обычным образом (если это возможно) .
inline int foo(int var)
{
...
}
inline int bar(int var)
{
...
}
inline int baz(int var)
{
...
}
Во-вторых, отправка этим функциям с помощью оператора switch
. На большинстве аппаратных средств большинство компиляторов могут генерировать очень эффективный код, обычно таблицу переходов, или, если она более эффективна, тщательно продуманные инструкции перехода.
switch(somestruct.callcondition)
{
case FOO_CASE:
foo(...);
break;
case BAR_CASE:
bar(...);
break;
case BAZ_CASE:
baz(...);
break;
}
Наконец, убедитесь, что ваш компилятор настроен на включение оптимизации . В режиме отладки большинство компиляторов не генерируют максимально эффективный код, потому что они пытаются сделать сгенерированный машинный код как можно более понятным, например, сохраняя порядок кода как можно ближе к источнику. Часто компилятор не выполняет встроенные вызовы, которые он мог бы, или не генерирует эффективные таблицы переходов, если вы специально этого не попросите.
тщательно составленные инструкции ветвления.switch(somestruct.callcondition)
{
case FOO_CASE:
foo(...);
break;
case BAR_CASE:
bar(...);
break;
case BAZ_CASE:
baz(...);
break;
}
Наконец, убедитесь, что ваш компилятор настроен на включение оптимизаций на . В режиме отладки большинство компиляторов не генерируют максимально эффективный код, потому что они пытаются сделать сгенерированный машинный код как можно более понятным, например, сохраняя порядок кода как можно ближе к источнику. Часто компилятор не выполняет встроенные вызовы, которые он мог бы, или не генерирует эффективные таблицы переходов, если вы специально этого не попросите.
тщательно составленные инструкции ветвления.switch(somestruct.callcondition)
{
case FOO_CASE:
foo(...);
break;
case BAR_CASE:
bar(...);
break;
case BAZ_CASE:
baz(...);
break;
}
Наконец, убедитесь, что ваш компилятор настроен на включение оптимизаций на . В режиме отладки большинство компиляторов не генерируют максимально эффективный код, потому что они пытаются сделать сгенерированный машинный код как можно более понятным, например, сохраняя порядок кода как можно ближе к источнику. Часто компилятор не выполняет встроенные вызовы, которые он мог бы, или не генерирует эффективные таблицы переходов, если вы специально этого не попросите.
Я знаю, что это закончилось неделю назад, но здесь все равно:
Во-первых, я не знаю, сработает ли это - но не могли бы вы просто написать макрос, похожий на вызов функции, который будет расширяться до встроенной инструкции перехода, чтобы буфер в соответствующей структуре? Это было бы похоже на переход к данным в структуре без вызова функции setup / teardown, но в коде это выглядело бы как вызов функции - но не было бы снижения производительности, потому что это будет обрабатываться препроцессором во время компиляции.
Но этот тип поведения программы - это то, что C является не подходит для. Как указывали другие ответы, код, который вы содержите в своем буфере, будет строго ограничен (без доступа к внешним / статическим переменным, без вызовов внешних функций и т.д.), он легко сломается (в случае защиты от невыполнения на страницах памяти , не имея возможности вернуться из-за того, что регистр ссылок не настроен, и т. д.), и может быть действительно непереносимым (также, вероятно, это будет ад для чтения - никто не сможет понять, что вы пытаясь обойтись без ударов головой об стол).
Не говоря уже о том, что использование буфера для хранения исполняемых данных - то же самое, что и указатель функции (поскольку буфер в любом случае является просто указателем на данные).
При этом C - довольно быстрый язык для начнем с - и компиляция с оптимизацией, вероятно, будет работать лучше, чем взлом на низком уровне (особенно с тем, что вы пытаетесь сделать). Прирост производительности (если он вообще есть) от попытки сохранить исполняемый код в буфере будет очень незначительным - и это только в тех случаях, когда он не работает медленнее. Такая производительность - более чем достаточно простой компромисс для удобочитаемости, переносимости и стабильности, которые вы получаете взамен.
Если вы пишете этот код для встраиваемой системы или чего-то еще, где производительность является такой щекотливой проблемой, вам следует подумать о написании этой части вашего приложения на ассемблере. Таким образом у вас будет больше контроля над поведением кода и вы сможете обойти соглашения о вызовах с помощью функций, внутренних по отношению к этой части. Вероятно, есть причина, по которой вы не можете делать что-то подобное в C: вам никогда не понадобится - и архитектура обычно не поддерживает это очень хорошо (если вообще поддерживает). Указатели на функции идеально подходят для желаемого здесь поведения, а функции-члены еще лучше. Если вы хотите стать еще лучше, вам придется написать сборку самостоятельно, иначе все развалится, и ее будет практически невозможно поддерживать.
теперь это еще старше , но у меня возникла другая мысль об этом: Вероятно, есть причина, по которой вы не можете делать что-то подобное в C: вам никогда не понадобится - и архитектура обычно не поддерживает это очень хорошо (если вообще поддерживает). Указатели на функции идеально подходят для желаемого здесь поведения, а функции-члены еще лучше. Если вы хотите стать еще лучше, вам придется написать сборку самостоятельно, иначе все развалится, и ее будет практически невозможно поддерживать.
теперь это еще старше , но у меня возникла другая мысль об этом: Вероятно, есть причина, по которой вы не можете делать что-то подобное в C: вам никогда не понадобится - и архитектура обычно не поддерживает это очень хорошо (если вообще поддерживает). Указатели на функции идеально подходят для желаемого здесь поведения, а функции-члены еще лучше. Если вы хотите стать еще лучше, вам придется написать сборку самостоятельно, иначе все развалится, и ее будет практически невозможно поддерживать.
теперь это еще старше , но у меня возникла другая мысль об этом: с указателями функций в структуре, чтобы инициализировать их, вы просто загружаете их с адресом, который уже известен компилятору.
Чтобы инициализировать буфер функции, который вы хотите, вы должны скопировать в буфер целую кучу исполняемых данных. Это настоящий хит производительности.
Вот решение на C для сохранения функции в буфер:
#include <stdio.h>
#define FUNC_SIZE(func) ((long)(& func##_end)-(long)(&func))
#define FUNC_COPY(func,buf) {buf=malloc(FUNC_SIZE(func));memcpy(buf,&func,FUNC_SIZE(func));}
int magic(int arg){
arg+=1;
arg-=1;
arg*=arg;
/* WHATEVER */
return arg;
}
void magic_end(){
return;
}
int main(int argc, char *argv[])
{
int (*buf)();
printf("Size of the function: %i\n",FUNC_SIZE(magic));
FUNC_COPY(magic,buf);
printf("Result: %i, %i\n",(*buf)(7),magic(7));
return 0;
}
Но я не думаю, что это быстрее, потому что вам все еще нужен указатель.