Это вопрос о нормах в стандарте C11, касающихся побочных эффектов, когда аргументы функции вычисляются в выражении.Хорошо сформированные пары при вызове функции
Я пытаюсь определить макрос в стандарте C, который эмулирует «метод» -подобие синтаксиса языка ООП в рудиментарной форме.
Я разработал решение, основные идеи которого я буду раскрывать здесь, но у меня есть некоторые сомнения относительно его соответствия C11.
Мне нужно сначала сделать экспозицию, и в конце я задам конкретный вопрос, связанный с оценкой выражений, связанных с вызовами функций. Извините, длинный пост.
Итак, учитывая struct
или колодцем struct *
объект x
, я был бы счастлив, если бы я мог сделать «метод» вызов, таким образом:
x->foo_method();
Типичный способ, что эта проблема решается что-то вроде этого:
Определить «класс» с помощью
struct
декларации:typedef struct foo_s { void foo_method(struct foo_s * this); } *foo_class; foo_class x = malloc(sizeof(struct foo_s)); x->foo_method = some_function_defined_over_there___;
Затем сделайте вызов, повторяя объект в этом «» параметр ``:
x->foo_method(x);
Можно попытаться определить какой-то «метод-вызов» макро:
#define call(X, M) ((X)->M(X))
Однако этот подход плох, поскольку оценка X
может дублировать побочные эффекты (это известная ошибка, повторяющая параметр макроса дважды).
[Используя сложные макросы можно обрабатывать случай произвольного числа параметров для метода M
, например, с помощью __VA_ARGS__
и несколько промежуточных макро-писак.]
Чтобы решить проблему повторения макро аргументов, я решил реализовать глобальный стек, может быть, скрытый в качестве статического массива в функции:
(void*) my_stack(void* x, char* operation)
{
static void* stack[100] = { NULL, };
// 'operation' selects "push" or "pop" operations on the stack.
// ...
// IF (operation == "push") then 'x' itself is returned again.
}
так что теперь, я избежать дублирования побочных эффектов в макро, на writtin `X» только один раз :
#define call(X, M) (((foo_class)my_stack((X), "push")) -> M (my_stack(0,"pop")))
Как вы можете видеть, мое намерение заключается в том, что функционально подобный макрос рассматривается C-компилятором как выражение, значением которого является значение, возвращаемое методом M
.
Я написал только один раз параметризатор X
внутри макроса, его значение было сохранено в стеке. Поскольку для этого значения нужно получить доступ к элементу «method» X
, это является причиной того, почему функция my_stack
возвращает значение x
: мне нужно его повторно использовать как часть того же выражения, которое нажало значение x
в стеке.
Хотя эта идея, кажется, легко решить проблему дублирования X
в call(X,M)
макро, они появляются больше проблем.
- Можно использовать «методы», аргументы которых также являются объектами, хранящимися в стеке, с использованием того же макроса
call()
. - Более того, у нас могут быть аргументы в «методе», значения которого получены в результате оценки других «методов».
- Наконец, другие функции или методы, являющиеся аргументами данного «метода», могут изменять стек, поскольку они, вероятно, являются функциями, изменяющими стек, используя макрос
call()
.
Я хочу, чтобы мои макросы были согласованы во всех этих случаях. Например, предположим, что x1,x2,x3
, являются объектами foo_class
.
С другой стороны, давайте предположим, что у нас есть, в foo_class
, следующий "метод" член:
int (*meth)(foo_class this, int, int);
Наконец, мы могли бы сделать "метод" вызов:
call(x1, meth, (call (x2, 2, 2), call(x3, 3, 3))) ;
[Настоящий синтаксис для макроса не является обязательным, поскольку он показывает это. Я полагаю, что основная идея понимается]
Цель состоит в том, чтобы эмулировать этот вызов функции:.
x1->meth(x1, x2->meth(x2,2,2), x3->meth(x3,3,3));
Проблема здесь состоит в том, что я использую стек для эмуляции следующих дупликаций объектов в звонок: x1->meth(x1,....)
, x2->meth(x2,...)
, x3->meth(x3,...)
.
Например: ((foo_class)(my_stack(x2,"push"))) -> meth (my_stack(0,"pop"), ...)
.
Мой вопрос: Могу ли я быть всегда уверен, что кожура «толчок»/«поп» в любом возможном выражении (т.е. последовательно использовать call()
макро) дает все время ожидаемой пары объектов?
Например, если я «толкаю» x2, было бы совершенно неправильно, что x3 «выскочил».
МОЯ догадка: Ответ будет ДА, но после глубокого анализа стандартного документа ISO C11 вокруг темы последовательности точек.
- Существует точка последовательности между выражением, которое выдает «метод» (фактически, «функция»), и выражения аргументов, которые должны быть переданы ему. Таким образом, например,
x1
хранится в стеке до того, как будет вызван методmeth
. - После оценки всех аргументов, переданных функции, и перед фактическим вызовом функции существует точка последовательности.
Таким образом, например, если новые объектыx4, x5
и т. Д.«выталкиваются»/«выталкиваются» в стек при вызовеx1->meth(x1...x2...x3)
, эти объектыx4
иx5
будут отображаться и исчезать в стеке послеx2, x3
, уже ушли из стека. Между аргументами в вызове функции нет точки последовательности.
Таким образом, следующие выражения могли чередоваться, когда они были оценены (когда они argurments вызова функции, указанные выше, с участиемx1,x2,x3
):my_stack(x2,"push") -> meth(my_stack(0,"pop"),2,2) my_stack(x3,"push") -> meth(my_stack(0,"pop"),3,3)
Это может случиться так, что после того, как объекты
x2
и быть «толкнул «в стопке операции« поп »могут случиться плохо в паре: может быть« вытолкнут »в строкеmeth(...,2,2)
, аx2
можно« выскочить »по линииmeth(...,3,3)
против желаемого.Эта ситуацияg вполне маловероятна, и кажется, что в стандарте C99 нет формального решения.
Однако в C11 у нас есть концепция побочных эффектов inderminately sequenced
.
У нас есть что:
Когда функция вызывается, все его побочные эффекты разрешаются indeterminaly секвенировали уважать любое другое выражение вокруг выражения, что делает вызов функции. [См. Пункт (12) здесь: sequence points].
Поскольку побочные эффекты при вызове функции из
meth
, участвующих в выражении:my_stack(x2,"push") -> meth(my_stack(0,"pop"),2,2)
должны решить «полностью перед» или «полностью после того, как» побочные эффекты в:
my_stack(x3,"push") -> meth(my_stack(0,"pop"),3,3)
Я пришел к выводу, что операции «push» и «pop» хорошо спарены.
В порядке ли моя интерпретация стандарта? я приведу его, на всякий случай:
[C11, 6.5.2.2/10] Существует точка последовательности после оценки функции целеуказателем и фактических аргументов, но до фактического вызова. Каждая оценка в вызывающей функции (включая вызовы функций), которая иначе не секретируется отдельно до или после выполнения тела вызываемой функции, неопределенно упорядочена по отношению к выполнению вызываемой функции.94)
[Сноска 94 ]: Другими словами, выполнение функций не «чередуется» друг с другом.
То есть, хотя порядок вычисления аргументов в вызове функции не может быть предсказан, я думаю, что можно, в любом случае, убедитесь, что правила «последовательность» тверд в ISO C11 достаточно для того, чтобы операции «push» и «pop» работают в этом случае.
Таким образом, синтаксис типа «method» может использоваться в C, чтобы эмулировать рудиментарную, но постоянную способность ООП «методов как членов объектов».
Во-первых, не указывайте указатели 'typedef'! Это просто приводит к путанице. Во-вторых: не слишком увлекайтесь макросами. Вы можете делать ООП в C, но есть линия, использующая другой язык, такой как C++ - лучший выбор. Помещение должно быть читабельностью/ремонтопригодностью (именно поэтому мы можем использовать ООП на самом деле). Я попадал в одну ловушку один или два раза. – Olaf
О, и ваш вопрос не очень подходит для переполнения стека, так как это скорее обсуждение, чем конкретный вопрос (по крайней мере, он привлечет оцененные комментарии/ответы. Это не дискуссионный форум, а сайт Q & A. ] – Olaf
@Olaf: Я знаю советы против использования ООП в C. Но иногда может потребоваться иметь хотя бы некоторый рудиментарный ООП на языке C, не изучая много C++, если будет использоваться только минимальное количество аспектов ООП Конечно, если нужно больше и более сложных методов ООП, использовать C++ является обязательным. – pablo1977