Подтвердить что ты не робот

Как заставить неиспользуемую память читать в C, которая не будет оптимизирована?

Микроконтроллеры часто требуют считывания регистра для очистки определенных состояний. Есть ли переносимый способ в C, чтобы убедиться, что чтение не оптимизировано, если данные не используются? Достаточно ли указывать указатель на регистр с отображением памяти как изменчивый? Другими словами, будет ли всегда выполняться работа со стандартными компиляторами?

void func(void)
{
   volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678;

   *REGISTER;
}

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

4b9b3361

Ответ 1

Люди спорят довольно энергично о именно то, что volatile означает. Я думаю, что большинство людей согласны с тем, что созданная вами конструкция предназначена для того, чтобы делать то, что вы хотите, но нет общего согласия в том, что язык в стандарте C фактически гарантирует его как на C99. (Возможно, в C2011 ситуация улучшилась, я еще этого не читал.)

Нестандартный, но довольно широко поддерживаемый встроенными компиляторами, альтернативой, который может с большей вероятностью работать, является

void func(void)
{
  asm volatile ("" : : "r" (*(unsigned int *)0x12345678));
}

( "volatile" здесь относится к "asm" и означает, что это может не быть удалено, даже если у него нет выходных операндов. Также нет необходимости помещать его в указатель.)

Основным недостатком этой конструкции является то, что у вас все еще нет гарантии, что компилятор будет генерировать чтение с одной инструкцией. С C2011 использование _Atomic unsigned int может быть достаточным, но в отсутствие этой функции вам в значительной степени придется писать реальную (непустую) вставку сборки, если вам нужна эта гарантия.

EDIT: Сегодня утром мне пришла в голову другая морщина. Если чтение из ячейки памяти имеет побочный эффект изменения значения в этой ячейке памяти, вам нужно

void func(void)
{
  unsigned int *ptr = (unsigned int *)0x12345678;
  asm volatile ("" : "=m" (*ptr) : "r" (*ptr));
}

чтобы предотвратить неправильную оптимизацию других чтений из этого местоположения. (Чтобы быть на 100% ясным, это изменение не изменит язык сборки, сгенерированный для самого func, но может повлиять на оптимизацию окружающего кода, особенно если func встроен.)

Ответ 2

Да, стандарт C гарантирует, что код, обращающийся к изменчивой переменной, не будет оптимизирован.

C11 5.1.2.3/2

"Доступ к неустойчивому объекту"... "- все побочные эффекты"

C11 5.1.2.3/4

"Фактическая реализация не должна оценивать часть выражения, если она может вывести, что ее значение не используется и что не создаются необходимые побочные эффекты (в том числе любые вызванные вызовом функции или доступом к неустойчивому объекту).

C11 5.1.2.3/6

"Наименьшие требования к соответствующей реализации:

- Доступ к неустойчивым объектам оценивается строго в соответствии с правилами абстрактной машины.

Ответ 3

IIRC, стандарт C немного ослаблен в определении использования, поэтому *REGISTER не обязательно интерпретируется как чтение.

Но следует сделать следующее:

int x = *REGISTER;

То есть результат ссылки на память должен использоваться где-то. Тем не менее, x не обязательно должен быть изменчивым.

UPDATE. Чтобы избежать предупреждения о переменной, которую вы использовали, вы можете использовать функцию no-op. Статическая и/или встроенная функция должна быть оптимизирована без штрафа за выполнение:

static /*inline*/ void no_op(int x)
{ }

no_op(*REGISTER);

ОБНОВЛЕНИЕ 2. Я только придумал более приятную функцию:

static unsigned int read(volatile unsigned int *addr)
{
    return *addr;
}

read(REGISTER);

Теперь эта функция может использоваться как для чтения и использования, так и для чтения и удаления. 8 -)

Ответ 4

Компиляторы обычно не оптимизируют сборку в строках (трудно анализировать их правильно). Более того, это похоже на правильное решение: вы хотите более явный контроль над регистрами, и это естественно для сборки.

Поскольку вы программируете микроконтроллер, я предполагаю, что в вашем коде уже есть сборка, поэтому немного встроенной сборки не будет проблемой.

Ответ 5

Возможно, специфические расширения GNU C не считаются очень переносимыми, но вот еще одна альтернатива.

#define read1(x)  \
({ \
  __typeof(x) * _addr = (volatile __typeof(x) *) &(x); \
  *_addr; \
})

Это приведет к следующей строке ассемблера (скомпилирован с gcc x86 и оптимизирован с -O2): movl SOME_REGISTER(%rip), %eax?

Я получаю тот же ассемблер из:

inline read2(volatile uint32_t *addr) 
{ 
   return *addr; 
}`

... как предлагается в другом ответе, но read1() будет обрабатывать разные размеры регистров. Хотя я не уверен, что когда-либо проблема с использованием read2() с 8 или 16-разрядными регистрами, по крайней мере, не будет никаких предупреждений о типе параметров.