Вопрос C: Если я передам адрес переменной функции, которая ее модифицирует, есть ли гарантия, что переменная будет «перезагружена» после возврата? - PullRequest
1 голос
/ 24 ноября 2010

У меня есть много кода, который был похож на это:

int num = 15;  

if(callback)  
  callback(&num);  /* this function may or may not change the value of num */  

if(num == 15)  /* I assumed num did not need to be volatile and is reloaded */  
  do_something();  
else  
  do_something_else();  

Однако теперь у меня есть более сложные структуры и указатели на них:

struct example { int x,y,z; };  
struct example eg, eg2, *peg;   
int whatever;  
char fun;  

struct variables { struct example **ppeg; int *whatever; char *fun; };  
struct variables myvars;  

peg = ⪚  
myvars.ppeg = &peg;  
myvars.whatever = &whatever;  
myvars.fun = &fun;  
[...]  
peg->x = 15;  

if(callback)  
  callback(&myvars);  /* this function may or may not change the variables */  

if(peg->x == 15)  /* Can I assume that x is "reloaded" ? */  
  do_something();  
else  
  do_something_else();  

Верьте или нет, это все еще слишком упрощено. Теперь, очевидно, я мог бы использовать volatile, я полагаю, я мог бы сделать что-то вроде этого, чтобы вызвать перезагрузку:

if(*(volatile int *)&peg->x == 15)  

Это гарантирует перезагрузку? Другими словами, смогу ли я потом написать просто, если (peg-> x), зная, что один изменчивый бросок уже «перезагрузил» переменную?

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

Кроме того, стандарт C99 имеет представление о любом из моих двух псевдосэмплов, например, чтобы гарантировать, что после функции переменные «перезагрузятся» (я не знаю правильного слова). Или это проблема компилятора и уровня оптимизации? Я проверил некоторые другие подобные разбавленные образцы с gcc при -O0 и -O3, и я не увидел разницы ни в одном случае (во всех случаях переменные имели свое правильное значение).

Спасибо всем!



РЕДАКТИРОВАНИЕ 24/24/2010 13:00 EST: чтобы ответить на комментарии о том, что я имею в виду под "перезагрузкой", я имел в виду, что компилятор кэширует переменную (например, в регистре или другом пространстве памяти) до того, как вызов функции все еще получит та же самая кэшированная переменная после вызова функции, а не (возможно) обновленная переменная.
Также компиляторы могут поднять цикл, чтобы удалить переменные из цикла, которые не меняются. Например, если у меня есть peg-> x, а не доступ к peg-> x каждый раз в цикле, может ли компилятор определить, что других обращений нет, даже если обратный вызов передан & peg? Если у меня есть такой код:

peg = ⪚  
while(1)  
{  
  if(!peg->x)  
    if(callback)  
      callback(peg);  

  if(!peg->x)  
    peg->x = 20;  

  if(peg == &eg)  
    peg = &eg2;  
  else  
    break;  
}  

Так может ли компилятор оптимизировать его, например, так:

while(1)  
{  
  if(!peg->x)  
  {  
    if(callback)  
      callback(peg);  

    peg->x = 20;  
  }  

  if(peg == &eg)  
    peg = &eg2;  
  else  
    break;  
}  

или это можно оптимизировать так:

{  
  int someregister;  
  peg = ⪚  
  someregister = peg->x;  

  if(!someregister)  
    if(callback)  
      callback(peg);  

  if(!someregister)  
    peg->x = 20;  

  peg = &eg2;  
  someregister = peg->x;  

  if(!someregister)  
    if(callback)  
      callback(peg);  

  if(!someregister)  
    peg->x = 20;  
}  



РЕДАКТИРОВАТЬ 24.11.2010 13:45 EST: вот еще один пример. Обратный вызов может изменить указатель на себя.

if(psomestruct->callback)  
{  
  psomestruct->callback(psomestruct); /* this callback could change the pointer to itself */  
  if(psomestruct->callback) /* will the compiler optimize this statement out? */  
    psomestruct->callback(psomestruct);  
}  

Ответы [ 2 ]

4 голосов
/ 24 ноября 2010

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

  • изменение переменной, которая была объявлена ​​const; и
  • изменение переменной через lvalue несовместимого типа, который не является unsigned char.

Так, например, если у вас есть:

void modifier(int *a)
{
    *a += 10;
}

тогда это всегда будет работать:

int i = 5;
modifier(&i);
if (i == 15)
    puts("OK");

Однако, это может не :

const int i = 5;
modifier((int *)&i);
if (i == 15)
    puts("OK");

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


Добавление:

Это охватывается стандартом C, но он не использует (бесполезный) подход, пытаясь перечислить каждую возможную оптимизацию и объявить, разрешена она или запрещена. Вместо этого стандарт C говорит (§5.1.2.3 в C99):

Семантические описания в этом Международный стандарт описывают поведение абстрактной машины в какие вопросы оптимизации являются не имеет значения.

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

Модификация объекта (даже через глубоко вложенный указатель в вызове функции) является побочным эффектом . В абстрактной машине C все побочные эффекты завершаются следующей точкой последовательности (в конце каждого выражения есть точка последовательности, например, после вызова callback() в вашем примере). Это также подробно описано в §5.1.2.3. Поскольку объекты в абстрактной машине C имеют только одно значение за один раз, после завершения модификации любые последующие чтения объекта должны прочитать новое значение.

Таким образом, нет необходимости в ignore_any_variables_previously_in_registers() - под абстрактной машиной C отсутствует концепция кэширования переменных в регистрах (или вообще, "регистры" вообще).

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

Для рассмотрения ваших новых примеров компилятору не разрешено выполнять те оптимизации, которые вы предлагаете. Если он ничего не знает о функции, назначенной указателю функции ->callback, он должен предположить, что он может изменить любую переменную , адрес которой возможно связан с указателем, доступным для ->callback(). На практике это, как правило, означает, что безопасными могут считаться только локальные переменные, адрес которых не был взят. Ключевое слово restrict было введено в C99, чтобы позволить программисту дать оптимизатору дополнительные гарантии в отношении такого псевдонима.

2 голосов
/ 24 ноября 2010

Нет такой вещи, как "перезагрузка" переменной. Вы можете опустить volatile, вы все равно получите то, что хотите.

Я думаю, вы видите проблему там, где ее нет.

...