2016-08-04 8 views
1

Это вопрос о нормах в стандарте 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, чтобы эмулировать рудиментарную, но постоянную способность ООП «методов как членов объектов».

+2

Во-первых, не указывайте указатели 'typedef'! Это просто приводит к путанице. Во-вторых: не слишком увлекайтесь макросами. Вы можете делать ООП в C, но есть линия, использующая другой язык, такой как C++ - лучший выбор. Помещение должно быть читабельностью/ремонтопригодностью (именно поэтому мы можем использовать ООП на самом деле). Я попадал в одну ловушку один или два раза. – Olaf

+1

О, и ваш вопрос не очень подходит для переполнения стека, так как это скорее обсуждение, чем конкретный вопрос (по крайней мере, он привлечет оцененные комментарии/ответы. Это не дискуссионный форум, а сайт Q & A. ] – Olaf

+0

@Olaf: Я знаю советы против использования ООП в C. Но иногда может потребоваться иметь хотя бы некоторый рудиментарный ООП на языке C, не изучая много C++, если будет использоваться только минимальное количество аспектов ООП Конечно, если нужно больше и более сложных методов ООП, использовать C++ является обязательным. – pablo1977

ответ

2

Нет, я не думаю, что можно гарантировать, что это делает то, что вы want.Let Разложат вашего выражение

my_stack(x2,"push") -> meth(my_stack(0,"pop"),2,2) 
<<<<<<A>>>>>>>>>>   <<<<<<<B>>>>>> 
<<<<<<<<<<<<<C>>>>>>>>>>> 
<<<<<<<<<<<<<<<<<<<<<<<D>>>>>>>>>>>>>>>>>>>>>>>> 

В оценках B и C являются полностью независимыми, и оба должны быть сделан до того, как вызов функции D. Аргументы функции и указателя функции для этого не сильно отличаются.

Поскольку A и B являются функциональными вызовами, они на самом деле секвенированы, но это неопределенно, поэтому вы не знаете, какой из них приходит первым и какой второй.

Я думаю, вам было бы намного лучше, если бы ваша call была встроенной функцией. Если вам действительно нужны версии call для разных типов, вы можете выбрать функцию с выражением _Generic. Но как кто-то уже сказал в комментариях, вы действительно находитесь на пределе того, что вы должны разумно делать в C.

+0

Спасибо за ваш ответ.Однако у меня есть сомнение, поскольку стандарт утверждает, что после обозначения функции есть точка последовательности. [6.5.2.2/10]: «После оценок указателя функции и фактических аргументов есть точка последовательности, но до фактического вызова. «Означает ли это, что оценки обозначения функции ** ** и некоторого ** аргумента ** могут быть выполнены в любом порядке? Например, сначала оценивается аргумент и, во-вторых, обозначение функции? – pablo1977

+1

Да, как вы можете видеть из текста, который вы цитируете, указатель функции и аргументы образуют одну группу непересекающихся выражений. В вашем случае они затем детерминированы последовательно, потому что они содержат вызовы функций сами. Но порядок не фиксирован. –

+0

@ pablo1977, я повторяю замечание Йенса, потому что вы теперь дважды делали то же ложное утверждение о нормах: нет точки последовательности между оценкой имени функции и оценкой аргументов функции. Стандарт говорит скорее, что есть точка последовательности после того, как * все * из них были оценены. Они могут быть оценены в любом относительном порядке. –

 Смежные вопросы

  • Нет связанных вопросов^_^