Вопросы с ограниченным указателем
Я немного запутался в правилах, касающихся ограниченных указателей. Может быть, кто-то может мне помочь.
Законно ли определять вложенные ограниченные указатели следующим образом:
int* restrict a; int* restrict b; a = malloc(sizeof(int)); // b = a; <-- assignment here is illegal, needs to happen in child block // *b = rand(); while(1) { b = a; // Is this legal? Assuming 'b' is not modified outside the while() block *b = rand(); }
Допустимо ли выводить значение ограниченного указателя следующим образом:
int* restrict c; int* restrict d; c = malloc(sizeof(int*)*101); d = c; for(int i = 0; i < 100; i++) { *d = i; d++; } c = d; // c is now set to the 101 element, is this legal assuming d isn't accessed? *c = rand();
Спасибо! Эндрю
2 ответа
Для справки, вот restrict
довольно запутанное определение классификатора (из C99 6.7.3.1 "Формальное определение ограничения"):
Пусть D будет объявлением обычного идентификатора, который предоставляет средство для обозначения объекта P в качестве ограниченно-квалифицированного указателя на тип T.
Если D появляется внутри блока и не имеет внешнего класса хранения, пусть B обозначает блок. Если D появляется в списке объявлений параметров определения функции, пусть B обозначает связанный блок. В противном случае пусть B обозначает блок main (или блок любой функции, вызываемой при запуске программы в автономной среде).
В последующем выражение указателя E называется основанным на объекте P, если (в некоторой точке последовательности при выполнении B до оценки E) модифицировать P так, чтобы он указывал на копию объекта массива, на который он ранее указывал изменит значение E. Обратите внимание, что "based" определяется только для выражений с типами указателей.
Во время каждого выполнения B пусть L будет любым lvalue, имеющим & L на основе P. Если L используется для доступа к значению объекта X, который он обозначает, и X также изменяется (любым способом), то применяются следующие требования: T не должен быть постоянным. Каждое другое lvalue, используемое для доступа к значению X, также должно иметь свой адрес на основе P. Каждый доступ, который изменяет X, должен также рассматриваться как модифицирующий P, для целей этого подпункта. Если P присваивается значение выражения E указателя, которое основано на другом объекте ограниченного указателя P2, связанном с блоком B2, то либо выполнение B2 должно начинаться до выполнения B, либо выполнение B2 должно заканчиваться до назначение. Если эти требования не выполняются, то поведение не определено.
Здесь выполнение B означает ту часть выполнения программы, которая соответствует времени жизни объекта со скалярным типом и длительности автоматического хранения, связанной с B.
Мое прочтение вышеизложенного означает, что в вашем первом вопросе a
не может быть назначен b
даже внутри дочернего блока - результат не определен. Такое назначение может быть сделано, если b
были объявлены в этом "подблоке", но так как b
объявляется в том же объеме, что и a
, назначение не может быть сделано.
Для вопроса 2, задания между c
а также d
также приводят к неопределенному поведению (в обоих случаях).
Соответствующий бит из стандарта (для обоих вопросов):
Если P присваивается значение выражения E указателя, которое основано на другом объекте ограниченного указателя P2, связанном с блоком B2, то либо выполнение B2 должно начинаться до выполнения B, либо выполнение B2 должно заканчиваться до назначение.
Поскольку ограниченные указатели связаны с одним и тем же блоком, блок B2 не может начинаться до выполнения B, или завершаться B2 до назначения (поскольку B и B2 являются одним и тем же блоком).
Стандарт дает пример, который проясняет это (я думаю - ясность restrict
4 коротких абзаца определения соответствуют правилам разрешения имен в C++:
ПРИМЕР 4:
Ограничения правил для ограниченных указателей не различают вызов функции и эквивалентный вложенный блок. За одним исключением, только "внешние-внутренние" назначения между ограниченными указателями, объявленными во вложенных блоках, имеют определенное поведение.
{ int * restrict p1; int * restrict q1; p1 = q1; // undefined behavior { int * restrict p2 = p1; // valid int * restrict q2 = q1; // valid p1 = q2; // undefined behavior p2 = q2; // undefined behavior } }
restrict
Спецификатор типа указывает компилятору, что, если память, адресованная restrict
квалифицированный указатель изменен, никакой другой указатель не получит доступ к той же памяти. Компилятор может решить оптимизировать код, включающий restrict
указатели, которые в противном случае могут привести к некорректному поведению. Программист несет ответственность за то, чтобы указатели с ограниченным доступом использовались так, как они предназначались для использования. В противном случае может возникнуть неопределенное поведение. ( ссылка)
Как видно из приведенного выше описания, оба ваших назначения недопустимы, что может работать в исполняемых файлах, созданных одними компиляторами, но нарушаться в других. Не ожидайте, что сам компилятор выдаст ошибки или предупреждения как restrict
просто дает возможность выполнить определенную оптимизацию, которую он может не выполнять, как в случае volatile
,