эмулируя thiscall в C для достижения структурных функций без самореференции - PullRequest
8 голосов
/ 23 декабря 2011

Это относится к потоку объектно-ориентированных сообщений C, но отличается тем, что мне не нужны все функции, только одна:

Возможность сделать это:

struct foo { 
  int (*bar)(void);
  char baz;
};

Тогда есть обратная ссылка.C имеет соглашение о вызовах cdecl;в основном помещает аргументы в стек, имеет указатель возврата, затем переходит на какой-либо адрес.Код по этому адресу извлекает аргументы из стека и идет своим путем.

Соглашение thiscall немного отличается тем, что добавляет один дополнительный аргумент, указатель "this", неявно.

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

int bar(void) {
  GETTHIS;
  cThis->baz = 0;
}

int createFoo(struct Foo*pFoo) {
  ASSIGN(pFoo, bar);
} 

По сути, что ASSIGNв некотором роде обойдется cdecl, чтобы эмулировать соглашение стиля thiscall, а затем то, что GETTHIS сделает, это другая сторона уловки учета.

Мне было интересно:

  • Решениек этому существует и
  • Если нет, если есть причина, по которой это невозможно сделать

Только в одиночку;удобство истинных функций-членов в стиле «мы все здесь взрослые», было бы просто потрясающе.Спасибо!

Примечания:

  • Я просто говорю здесь о x86, linux, gcc ... Я знаю, что мир - это широкое и странное место.
  • Это из чистого любопытства .

Ответы [ 3 ]

4 голосов
/ 24 декабря 2011

Мой друг, в конце концов, понял: https://gist.github.com/1516195 ... требует указания функции арности и ограничен x86_64 ... но да, это довольно хороший компромисс, который делает вещи действительно ненавязчивыми.

4 голосов
/ 23 декабря 2011

Первая небольшая коррекция:

C имеет соглашение о вызовах cdecl; в основном выдвигает аргументы в стек, имеет указатель возврата, затем переходит на какой-то адрес. код по этому адресу извлекает аргументы из стека и продолжает его веселый путь.

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

Теперь по вашему вопросу. Не существует строго определенного thiscall соглашения о вызовах. Подробнее здесь .

Gcc специфические:

thiscall почти идентичен cdecl: вызывающая функция очищает стек, и параметры передаются в порядке справа налево. Разница заключается в добавлении указателя this, который помещается на стек последний, как если бы это был первый параметр в функции Прототип

Вы не сможете выполнить свой трюк с сокрытием дополнительного аргумента this и извлечением его внутри тела функции, особенно потому, что this является первым аргументом функции. Если вы это скрываете - все остальные параметры функции будут смещены. Вместо этого вы можете (и должны ИМХО) объявить его как первый аргумент функции, и у вас будет прямой доступ к нему, без необходимости дополнительных уловок:

int bar(struct foo *cThis) {
  cThis->baz = 0;
}

MSVC-конкретны:

... этот указатель передается в ECX, и вызываемый объект очищает стек, отражающий соглашение stdcall, используемое в C для этого компилятор и функции Windows API. Когда функции используют переменную количество аргументов, это вызывающий, который очищает стек (ср. Cdecl).

Здесь вы можете выполнить свой трюк, скопировав значение регистра ECX в локальную переменную. Примерно так:

#define GETTHIS \
struct foo *cThis; \
_asm { \
    mov cThis, ecx \
};

Это, однако, сложно и не всегда может работать. Причина этого заключается в том, что в соответствии с соглашениями о вызовах thiscall / stdcall регистр ECX зарезервирован для использования функции, поэтому компилятор может сгенерировать код, который перезаписывает значение регистра ECX. Это может произойти даже до того, как вы вызовете макрос GETTHIS.

3 голосов
/ 23 декабря 2011

Нет, в Си нет поддержки "thiscall", то есть только для C ++.

Здесь также нет секретной уловки, только некоторый синтаксический сахар.

Когда вы пишете

class foo {
public:
    int bar();
    char baz;
 }; 

компилятор концептуально переписывает это как

struct foo {
    char baz;
 }; 

 int bar(struct foo* this);

, а затем, когда вы делаете

foo   F;
F.bar();

, это компилируется как эквивалент

struct foo   F;
bar(&F);

Это все, что нужно!

Если вы хотите играть с Линусом, вам просто нужно сделать это вручную, потому что он не доверяет вам не использоватьвиртуальное наследование, как только вы наберете g ++ вместо gcc.

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