Пытаясь ответить на недавний вопрос (Передача ограниченных квалифицированных указателей на функции?) , я не смог найти насколько стандарт С11 согласуется с практикой.
Я не пытаюсь ссылаться на стандарт или что-то в этом роде, большинство вещей, которые выглядят непоследовательными, я просто неправильно понимаю, но мой вопрос лучше всего формулировать как аргумент против определения, используемого в стандарте, так что вот оно.
Кажется общепризнанным, что функция может принимать ограниченный квалифицированный указатель и работать с ним, и с ним работают ее собственные вызовы функций. Например,
// set C to componentwise sum and D to componentwise difference
void dif_sum(float* restrict C, float* restrict D, size_t n)
{
size_t i = 0;
while(i<n) C[i] = C[i] - D[i],
D[i] += C[i] + D[i],
++i;
}
// set A to componentwise sum of squares of A and B
// set B to componentwise product of A and B
void prod_squdif(float* restrict A, float* restrict B, size_t n)
{
size_t i = 0;
float x;
dif_sum(A,B,n);
while(i<n) x = ( (A[i]*=A[i]) - B[i]*B[i] )/2,
A[i] -= x,
B[i++] = x/2;
}
Что кажется общим пониманием, так это то, что ограничивающие указатели должны ссылаться на независимое пространство в своем блоке объявления. Таким образом, prod_sqdif действителен, потому что ничто лексически в его определяющем блоке не обращается к массивам, идентифицированным A или B, кроме этих указателей.
Чтобы продемонстрировать мою озабоченность стандартом, вот стандартное формальное определение ограничения (согласно проекту комитета, если у вас есть окончательная версия и она отличается, дайте мне знать!):
6.7.3.1 Формальное определение ограничения
1 Пусть D будет объявлением обычного идентификатора, который предоставляет средства для обозначения объекта P как указателя с ограничением на тип T.
2 Если D появляется внутри блока и не имеет внешнего класса хранения, пусть B обозначает блок. Если D появляется в списке объявлений параметров определения функции, пусть B обозначает соответствующий блок. В противном случае пусть B обозначает блок main (или блок любой функции, вызываемой при запуске программы в автономной среде).
3 В дальнейшем говорят, что выражение указателя E основано на объекте P, если (в какой-то момент выполнения B перед вычислением E) P модифицируется так, чтобы он указывал на копию объекта массива, в котором он ранее point изменит значение E. Обратите внимание, что «основанный» определен только для выражений с типами указателей.
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.
6 Переводчик волен игнорировать любое или все последствия использования слова limited, связанные с псевдонимами.
[Примеры не включены, поскольку они не имеют формального значения.]
Идентификация выполнения B с лексически содержащимися в нем выражениями может рассматриваться как поддерживаемая следующей выдержкой из 6.2.4, пункт 6:
"...Вход во вложенный блок или вызов функции приостанавливает, но не заканчивает выполнение текущего блока..."
Однако в части 5 формального определения ограничения блок B явно определяется как соответствующий сроку жизни объекта с автоматическим хранением, объявленным в B (в моем примере B — это тело prod_squdif). Это явно переопределяет любое определение выполнения блока, найденное в другом месте стандарта. Следующая выдержка из стандарта определяет время жизни объекта.
6.2.4 Сроки хранения объектов, п.2
Время жизни объекта — это часть выполнения программы, в течение которой для него гарантированно резервируется память. Объект существует, имеет постоянный адрес и сохраняет свое последнее сохраненное значение на протяжении всей своей жизни. 34) Если на объект ссылаются за пределами его времени жизни, поведение не определено. Значение указателя становится неопределенным, когда объект, на который он указывает (или только что прошедший), достигает конца своего времени существования.
Тогда выполнение dif_sum явно входит в выполнение B. Не думаю, что тут есть какие-то вопросы. Но тогда значения lvalue в dif_sum, которые считывают и изменяют элементы A и B (через C и D), явно не основаны на A и B (они следуют точкам последовательности, где A и B могли быть переназначены на копии их содержимого без изменения местоположения, идентифицированные lvalues). Это неопределенное поведение. Обратите внимание, что то, какие значения lvalue или точки последовательности обсуждаются в пункте 4, не ограничено; как указано, нет причин ограничивать lvalue и порядковые точки теми, которые лексически соответствуют блоку B, и поэтому lvalue и порядковые точки внутри вызова функции играют точно так же, как и в теле вызывающей функции.
С другой стороны, общепринятое использование ограничения, по-видимому, подразумевается тем фактом, что формальное определение явно позволяет присваивать C и D значения A и B. Это предполагает, что некоторый осмысленный доступ к A и B через C и D является разрешается. Однако, как утверждалось выше, такой доступ не определен для любого элемента, измененного с помощью внешнего или внутреннего вызова функции и, по крайней мере, прочитанного с помощью внутреннего вызова. Это кажется противоречащим очевидному намерению разрешить присвоение в первую очередь.
Конечно, у намерения нет формального места в стандарте, но кажется наводящим на мысль, что подразумевалось общепринятое толкование ограничения, а не то, что кажется фактически определенным.
Таким образом, интерпретируя выполнение B как выполнение каждого оператора в течение времени существования автоматического хранилища B, вызовы функций не могут работать с содержимым переданных им указателей ограничения.
Мне кажется неизбежным, что должно быть какое-то исключение, указывающее, что чтение и запись внутри функций или подблоков не учитываются, но самое большее одно назначение в таком подблоке (и других подблоках, рекурсивно) может быть основано на каком-либо конкретном ограничении. указатель во внешнем блоке.
Я действительно превысил стандарт, и сегодня, и вчера. Я действительно не понимаю, как формальное определение ограничения может согласовываться с тем, как оно понимается и реализуется.
РЕДАКТИРОВАТЬ: Как уже отмечалось, нарушение контракта с ограничением приводит к неопределенному поведению. Мой вопрос не о том, что происходит, когда контракт нарушается. Мой вопрос можно переформулировать так:
Как формальное определение ограничения может быть согласовано с доступом к элементам массива через вызовы функций? Разве такой доступ внутри вызывающей функции не представляет собой доступ, не основанный на указателе ограничения, переданном функции?
Я ищу ответ, основанный на стандарте, поскольку я согласен с тем, что указатели ограничения должны передаваться через вызовы функций. Просто кажется, что это не следствие формального определения в стандарте.
РЕДАКТИРОВАТЬ
Я думаю, что основная проблема с передачей моего вопроса связана с определением «на основе». Здесь я попытаюсь изложить свой вопрос немного иначе.
Ниже приведено неофициальное отслеживание конкретного вызова prod_squdif. Это не код C, это просто неформальное описание выполнения функционального блока.
Обратите внимание, что это выполнение включает в себя выполнение вызываемой функции в соответствии с пунктом 5 формального определения ограничения: «Здесь выполнение B означает ту часть выполнения программы, которая соответствует времени жизни объекта со скалярным типом и продолжительность автоматического хранения, связанная с B."
// 1. prod_squdif is called
prod_squdif( (float[1]){2}, (float[1]){1}, 1 )
// 2. dif_sum is called
dif_sum(A,B,n) // assigns C=A and D=B
// 3. while condition is evaluated
0<1 // true
// 4. 1st assignment expression
C[0] = C[0] - D[0] // C[0] == 0
// 5. 2nd assignment expression
D[0] += C[0] + D[0] // D[0] == 1
// 6. increment
++i // i == 1
// 7. test
1<1 // false
// return to calling function
// 8. test
0<1 // true
// 9. 1st assignment expression
x = ( (A[0]*=A[0]) - B[1]*B[1] )/2 // x == -.5
// 10. 2nd assignment expression
A[0] -= -.5 // A[0] == .5
// 11. 3rd assignment expression
B[i++/*evaluates to 0*/] = -.5/2 // B[0] == -.25
// 12. test
1<1 // false
// prod_squdif returns
Итак, тест на ограничение контракта дается пунктом 4 в формальном определении ограничения: «Во время каждого выполнения B пусть L будет любым lvalue, имеющим &L на основе P. Если L используется для доступа к значению объекта X, который он обозначает, и X также изменен (любым способом), то применяются следующие требования: ... Каждое другое lvalue, используемое для доступа к значению X, также должно иметь свой адрес, основанный на P..."
Пусть L будет lvalue слева от части выполнения, отмеченной '4' выше (C[0]). Основан ли &L на A? То есть, C основан на A?
См. пункт 3 формального определения ограничения: «... говорят, что выражение указателя E основано на объекте P, если (в какой-то момент выполнения B перед вычислением E) изменение P так, чтобы оно указывало на a копия объекта массива, на который он ранее указывал, изменит значение E...".
Возьмем за точку последовательности конец пункта 3 выше. (В этой точке последовательности) изменение A для указания на копию объекта массива, на который он ранее указывал, НЕ изменит значение C.
Таким образом, C не основан на A. Таким образом, A[0] модифицируется lvalue, не основанным на A. Поскольку он также считывается lvalue, основанным на A (элемент 10), это поведение undefined.
Мой вопрос: правильно ли поэтому сделать вывод, что мой пример вызывает неопределенное поведение и, следовательно, формальное определение ограничения не согласуется с общей реализацией?
dif_sum
частью блока, в котором оно вызывается. Если это так, ваше предварительное условие уже неверно. - person too honest for this site   schedule 01.04.2016prod_dif
неопределенным. Я буду рад включить все, что, по вашему мнению, может помочь. - person Kyle   schedule 01.04.2016A_
были назначеныA
. (т.е.A = A_;
) Но на самом деле ваш код этого не делает. И это никоим образом не актуально. Вы не предоставите пример кода, поэтому я предполагаю, что вы описываете что-то вродеdif_sum(A,B,n); A = A_;
Но поведение кода вdif_sum
определялось тем, что происходит до этого момента, а не тем, что происходит потом.C
основано на том, что былоA
, когда вы вызвалиdif_sum
, а не на каком-либо последующем назначении.C
даже не существует к моменту достиженияA = A_;
. - person M.M   schedule 01.04.2016A = A_;
перед вызовомdif_sum
означало бы, чтоC
имеет другое значение, чем оно было бы, если бы вы не написали это, поэтомуC
основано наA
. (об этом говорит пункт 3) - person M.M   schedule 01.04.2016C
, для которого изменение значенияA
меняет значениеC
? Если да, тоC
основано наA
. - person EOF   schedule 01.04.2016A
имеет значение, на которое вы изменили его на шаге 3, поэтому элемент 10 не считывает тот же объект, который изменяет элемент4
. - person M.M   schedule 01.04.2016