2010-01-10 5 views
3

Я пытаюсь решить, следует ли выполнять определенные операции как макросы или как функции.globals, код повторного входа и безопасность потоков

Скажем, только в качестве примера, у меня есть следующий код в файле заголовка:

extern int x_GLOB; 
extern int y_GLOB; 

#define min(x,y) ((x_GLOB = (x)) < (y_GLOB = (y))? x_GLOB : y_GLOB) 

намерение состоит в том, чтобы каждый отдельный аргумент, вычисленный только один раз (min(x++,y++) не вызовет никаких проблем здесь).

Вопрос заключается в следующем: выполняют ли две глобальные переменные какие-либо проблемы с точки зрения повторного ввода кода или безопасности потоков?

Я бы сказал нет, но я не уверен.

А что:

#define min(x,y) ((x_GLOB = (x)), \ 
        (y_GLOB = (y)), \ 
        ((x_GLOB < y_GLOB) ? x_GLOB : y_GLOB) 

бы это быть различный случай?

Обратите внимание, что вопрос не является «вообще», а связан с тем фактом, что эти глобальные переменные используются только внутри этого макроса и для оценки одного выражения.

Во всяком случае, глядя на ответы на приведенные ниже, я думаю, можно резюмировать следующим образом:

  • Это не поточно, как ничто не гарантирует, что поток приостановлен «в середине» оценки выражения (как я и надеялся)

  • «Состояние» этих глобальных символов представляет собой, по крайней мере, внутреннее состояние операции «мин» и сохранение этого состояния, по крайней мере, потребовало бы накладывать ограничения на то, как функция можно назвать (например, избегать мин (мин (1,2), мин (3,1)).

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

+0

Как будет оцениваться min (min (a, b), min (c, d))? –

+0

Точка взята! Использование глобалов потребовало бы постановки ограничения на то, как эта «функция» должна использоваться. Чанки –

ответ

1

Лично я не думаю, что этот код безопасен вообще, даже если мы не рассматриваем многопоточные сценарии.

Возьмем следующую оценку:

int a = min(min(1, 2), min(3, 4)); 

Как это будет расширяться должным образом и оценить?

  1. Оценить мин (1, 2) и присвоить значение x_GLOB (внешний макро):
    • x_GLOB = 1
    • y_GLOB = 2
    • оценки мин, результат равен 1, назначить x_GLOB (для внешней макро)
  2. Оценка мин (3, 4) и присвоить значение y_GLOB (внешний макро):
    • x_GLOB = 3 (мм о, 1 из первого расширения теперь затерт)
    • y_GLOB = 4
    • оценки мин, результат 3, назначить y_GLOB (для внешней макро)
  3. Оценка мин (а, б) где а мин (1, 2) и Ь мин (3, 4)
    • оценки мин x_GLOB (который является 3) и y_GLOB (который является 3)
    • результат общая оценка 3

Или я чего-то не хватает?

2

Глобалы не являются потокобезопасными. Вы можете сделать их так, используя thread-local storage.

Лично я использовал бы встроенную функцию вместо макроса, чтобы избежать проблемы вообще!

+0

Согласен. Макросы лучше всего избегать и только хороши, если вы используете компилятор, который не соблюдает вложение или плохо влияет на inlining. – martinr

+0

Потоковое локальное хранилище дорого; может быть отключен с использованием временных локальных переменных. Имена местных жителей могут быть переданы макросу. – martinr

+0

Есть две части этого вопроса: Reentrancy и безопасность потоков. Reentrancy имеет список правил (см. Ссылку на wikipedia ниже), и безопасность потоков требует либо атомарности (поддерживаемой на аппаратном уровне), либо синхронности на уровне ОС. Thread-local storage + встроенные функции - один из способов обеспечить обе, но не обязательно лучшую или самую быструю комбинацию для всех ситуаций. –

1

Вообще говоря, в любое время, когда вы используете глобальные переменные, ваша подпрограмма (или макрос) не является повторной.

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

См Wikipedia reentrant страница

0

Этот код не является безопасным для многопоточности.

Просто избегайте макросов, если только они не являются необходимыми (в примерах этих случаев есть книги «Efficient C++»).

4

Модифицировать глобальные переменные внутри макроса как min - очень плохая идея.Вы гораздо лучше

  • принимающему, что min может оценить его аргумент несколько раз (в этом случае я бы назвал это MIN, чтобы сделать его менее, как регулярная функция C), или
  • написать регулярное или встроенная функция для min, или
  • Используйте gcc-расширения, чтобы сделать макрос безопасным.

Если вы делаете что-либо из этого, вы полностью исключаете глобальные переменные и, следовательно, все вопросы о реентракте и многопоточности.

Вариант 1:

#define MIN(x, y) ((x) < (y) ? (x) : (y)) 

Вариант 2:

int min(int x, int y) { return x < y ? x : y; } 

Конечно, это есть проблема, что вы не можете использовать эту версию min для различных типов аргументов.

Вариант 3:

#define min(x, y) ({ \ 
    typeof(x) x__ = (x); \ 
    typeof(y) y__ = (y); \ 
    x__ < y__ ? x__ : y__; \ 
}) 
+0

+1; следует избегать начинать имена переменных с двойными подчеркиваниями, хотя ... – Christoph

+0

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

+0

Возможно, стоит отметить, что 'typeof' (используется в опции 3) является только GCC. – martinr

1

Это не поточно-. Чтобы продемонстрировать это, предположим, что поток A вызывает min (1,2) и threadB вызовы min (3,4). Предположим, что поток A запущен первым и прерван планировщиком прямо на вопросительном знаке макроса, поскольку его временной срез истек.

Тогда x_GLOB является 1, а y_GLOB равно 2.

Теперь предположим, что threadB проходит некоторое время, и завершает содержимое макроса:

x_GLOB является 3, y_GLOB равно 4.

Теперь предположим, что threadA возобновляется. Он вернет x_GLOB (3) вместо правильного ответа (1).

Очевидно, что я немного упростил ситуацию - прерывание «на вопросительном знаке» довольно практично, и threadA не обязательно возвращает 3, на некоторых компиляторах с некоторыми уровнями оптимизации он может сохранять значения в регистре, а не сначала прочитайте от x_GLOB. Таким образом, испускаемый код может работать с этим компилятором и параметрами. Но, надеюсь, мой пример указывает на то, что вы не можете быть уверены.

Я рекомендую вам написать статическую встроенную функцию. Если вы пытаетесь быть очень компактны, сделать это следующим образом:

#define STATIC_INLINE static 

STATIC_INLINE int min_func(int x, int y) { return x < y ? x : y; } 

#define min(a,b) min_func((a),(b)) 

Тогда, если у вас есть проблемы с производительностью на какой-то конкретной платформе, и это оказывается потому, что компилятор не может встраивать его, вы можете беспокоиться о том, нужно ли в этом компиляторе определить STATIC_INLINE по-разному (до static inline в C99 или static __inline для Microsoft или что-то еще), или, возможно, даже реализовать макрос min по-разному. Этот уровень оптимизации («он встраивается?») Не является чем-то, что вы можете многое сделать в переносном коде.

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