Гранулярность квалификатора limited для перекрывающихся указателей, типов

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

int* arr_ptr0 = &arr[0];
int* arr_ptr1 = &arr[1];
for (int i=0;i<10;++i) {
    *arr_ptr0 = *arr_ptr1;
    arr_ptr0 += 2;
    arr_ptr1 += 2;
}

Дело в том, что эти указатели на самом деле действительно указывают на перекрывающуюся память! Для этого конкретного примера такие руководства, как это говорит, например:

Это действительно. . . указывать на один и тот же объект массива при условии, что диапазон элементов, доступ к которым осуществляется через один из указателей, не перекрывается с диапазоном элементов, к которым осуществляется доступ через другой указатель.

Мой вопрос: Какая степень детализации "элементов"?

Например, предположим, что у меня есть массив типа struct Foo. Нужно ли мне гарантировать, что я не буду обращаться к одному и тому же диапазону элементов (Foos), даже если части, к которым я обращаюсь, не пересекаются? Вот простой скалярный пример:

struct Foo { int i; float f; };
void f(struct Foo*restrict foo0, struct Foo*restrict foo1) {
    foo0->i = 6;
    foo1->f = 19.0f;
}
void g(struct Foo* foo) {
    f(foo,foo); /* problem? */
}

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


person imallett    schedule 19.09.2014    source источник
comment
AFAIUI, да, вам действительно нужно убедиться, что вы не получаете доступ к одной и той же структуре, независимо от того, к какой части структуры вы обращаетесь, через два разных указателя. Это даже не вопрос «одновременности»; это «тот же вызов функции». Таким образом, если вы обращаетесь к ptr1[a] .. ptr1[b] (a ‹ b) и ptr2[c] .. ptr2[d] (c ‹ d) и любой из адресов в первом диапазоне совпадает с любым из адресов во втором диапазоне, ограничение ограничения нарушается.   -  person Jonathan Leffler    schedule 20.09.2014
comment
@JonathanLeffler: это не тот же вызов функции, а время жизни выполнения определенного блока, связанное со временем жизни указателя с указанием restrict.   -  person R.. GitHub STOP HELPING ICE    schedule 20.09.2014


Ответы (2)


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

1 Пусть D будет объявлением обычного идентификатора, который предоставляет средства для обозначения объекта P как указателя с ограничением на тип T.

2 Если D появляется внутри блока и не имеет внешнего класса хранения, пусть B обозначает блок. Если D появляется в списке объявлений параметров определения функции, пусть B обозначает соответствующий блок. В противном случае пусть B обозначает блок main (или блок любой функции, вызываемой при запуске программы в автономной среде).

3 В дальнейшем говорят, что выражение указателя E основано на объекте P, если (в какой-то момент выполнения B перед вычислением E) P модифицируется так, чтобы он указывал на копию объекта массива, в котором он ранее point изменит значение E.137) Обратите внимание, что «основанный» определен только для выражений с типами указателей.

4 Во время каждого выполнения B пусть L будет любым значением l, которое имеет &L на основе P. Если L используется для доступа к значению объекта X, которое он обозначает, и X также модифицируется (любыми способами), то следующие требования apply: T не должен быть const-квалифицированным. Любое другое lvalue, используемое для доступа к значению X, также должно иметь свой адрес, основанный на P. Каждый доступ, который изменяет X, также должен рассматриваться как модифицирующий P для целей настоящего подпункта. Если P присваивается значение выражения указателя E, которое основано на другом ограниченном объекте указателя P2, связанном с блоком B2, то либо выполнение B2 должно начаться до выполнения B, либо выполнение B2 должно закончиться до выполнения B2. назначение. Если эти требования не выполняются, то поведение не определено.

5 Здесь выполнение B означает ту часть выполнения программы, которая соответствует времени жизни объекта скалярного типа и продолжительности автоматического хранения, связанного с B.

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

Второй пример со структурой менее ясен и зависит от того, означает ли использование оператора -> (вы написали ., но имели в виду ->) с foo0 (или foo1) что *foo0 (или foo1) используется для доступа к значению объекта что оно обозначает. Мне это непонятно, так как структура не используется как значение; только его члены.

person R.. GitHub STOP HELPING ICE    schedule 19.09.2014
comment
если вы не рассматриваете доступ к членам как доступ к агрегату, некоторые части стандарта C (например, restrict, как мы видели, но также и _Atomic) становятся очень... скажем, "интересными"; в случае изменения доступа, возможно, ясно, что доступ к члену подразумевает доступ к агрегату: если значение члена изменяется, то значение агрегата также изменяется, подразумевая, что доступ к нему был осуществлен по определению; если предположить, что цель стандарта не в том, чтобы полностью свести с ума программистов на C, то же самое должно быть справедливо и для неизменяющего доступа, но жюри по этому вопросу еще не принято;) - person Christoph; 20.09.2014
comment
хм... вычеркните _Atomic из моего аргумента, так как вам все равно не разрешен доступ к элементам атомарных структур; однако этот аргумент верен для volatile; также обратите внимание, что противоположный случай (доступ к агрегату подразумевает доступ к элементу) получает явное упоминание в действующих правилах типизации (но, конечно, не имеет значения) - person Christoph; 20.09.2014
comment
Не будет ли L быть любым lvalue, у которого &L основано на P. Если L используется для доступа к значению объекта X, который он обозначает, и X также изменяется (любыми способами) и < i>Каждый доступ, который изменяет X, должен также рассматриваться как модифицирующий P для целей настоящего подпункта. Если P присваивается значение выражения указателя E, которое основано на другом ограниченном объекте указателя P2, связанном с блоком B2, то либо выполнение B2 должно начаться до выполнения B... подразумевает параллельную модификацию в том же блоке для второго примера? - person Jason; 20.09.2014

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

На самом деле ничто не ограничивает приложение от этого. Однако можно с уверенностью предположить, что изменение адреса, доступ к которому осуществляется через квалифицированный restrict указатель, с помощью чего-то другого, кроме этого restrict квалифицированного указателя, приведет к поведению undefined (остерегайтесь драконов).

person Jason    schedule 19.09.2014