Это вопрос о нормах стандарта 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___;
Затем сделайте вызов, повторив объект в параметре ``this'':
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.
}
Итак, теперь я избегаю дублирования побочных эффектов в макросе, написав `X' только один раз:
#define call(X, M) (((foo_class)my_stack((X), "push")) -> M (my_stack(0,"pop")))
Как видите, мое намерение состоит в том, чтобы макрос, похожий на функцию, рассматривался компилятором C как выражение, значение которого является значением, возвращаемым методом M
.
Я только один раз написал параметр X
внутри макроса. -body, его значение сохранялось в стеке. Поскольку нужно, чтобы это значение имело доступ к члену «метода» самого 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"), ...)
.
МОЙ ВОПРОС: Могу ли я всегда быть уверенным, что объединение "push"/"pop" в любом возможном выражении (которое постоянно использует макрос call()
) всегда дает ожидаемую пару объектов?
Например, если я «нажимаю» x2, было бы совершенно неправильно, чтобы x3 «выталкивался».
МОЕ ПРЕДЛОЖЕНИЕ: Ответ будет ДА, но после глубокого анализа стандартного документа ISO C11 по теме точек последовательности.
- Существует точка последовательности между выражением, которое создает "метод" (фактически, "функцию"), который нужно вызвать, и выражениями аргументов, которые должны быть переданы ему. Таким образом, например,
x1
сохраняется в стеке до того, как методmeth
считается вызванным. - Существует точка следования после оценки всех аргументов, переданных в функцию, и перед фактическим вызовом функции.
Таким образом, например, если новые объектыx4, x5
и т. д. "заталкиваются"/"извлекаются" в стек во время происходит вызовx1->meth(x1...x2...x3)
, эти объектыx4
иx5
будут появляться и исчезать в стеке после того, какx2, x3
уже ушли из стека. Между аргументами в вызове функции нет точки последовательности.
Таким образом, следующие выражения могут чередоваться при их вычислении (когда они являются аргументами вызова функции, показанного выше, включая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
иx3
были "задвинуты" в стек, операции "выталкивания" могли происходить непарно:x3
могло "выталкиваться" в строкеmeth(...,2,2)
, аx2
могло "выталкиваться" в строкеmeth(...,3,3)
. , против желаемого.Эта ситуация совершенно маловероятна, и кажется, что в Стандарте C99 нет формального решения.
Однако в C11 у нас есть концепция inderminately sequenced
побочных эффектов.
У нас есть следующее:
Когда вызывается функция, все ее побочные эффекты разрешаются в неопределенной последовательности относительно любого другого выражения вокруг выражения, которое вызывает вызов функции. [См. параграф (12) здесь: точки последовательности].
Так как побочные эффекты при вызове функции
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, достаточно для того, чтобы гарантировать, что «толчок» и « pop" хорошо работают в этом случае.
Таким образом, синтаксис, подобный «методу», может использоваться в C, чтобы эмулировать рудиментарную, но последовательную возможность ООП «методы как члены объектов».
typedef
указатели! Это просто приводит к путанице. Второе: не увлекайтесь макросами. Вы можете использовать ООП в C, но есть линия, использующая другой язык, такой как C++, является лучшим выбором. Предпосылка должна быть удобочитаемостью/обслуживаемостью (поэтому мы могли бы использовать ООП на самом деле). Я попал в одну и ту же ловушку раз или два. - person too honest for this site   schedule 04.08.2016