Какова семантика “restrict” C99 в отношении указателей на указатели?

StackOverflow https://stackoverflow.com/questions/1449823

Вопрос

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

Я бы хотел настроить свои матрицы как указатели на указатели, чтобы обеспечить легкую подписку, вот так:

int **A = malloc (ncols * sizeof(int *));
A[0] = malloc (nrows * ncols * sizof(int));
for (int i=1; i < ncols; i++) {
    A[i] = A[0] + i*nrows;
}

Теперь, для функции умножения матрицы

void mmultiply ( int nrows, int ncols, int **Out, int **A, int **B);

должен ли я квалифицировать оба указателя аргументов как ограниченные?Это допустимый синтаксис, но мне трудно определить, является ли int *restrict *restrict ведет себя не так, как int **restrict.

Затем, при надлежащем ограничении указателей, осуществляется доступ к элементам через A[0][col*nrows + row] неопределенный?(т.е. будет ли компилятор предполагать, что я Только получите доступ к матрице через A[col][row] для значений row такой , что row < nrow)?Или я должен просто оставаться последовательным?

Это было полезно?

Решение

Что касается первого вопроса, "да", то это будет означать что-то другое, если вы используете оба restrict уточняющие, в частности, то, что указатели также не будут иметь псевдонимов.Относительно того, имеет ли это какое-либо значение:теоретически да, на практике это зависит от оптимизатора.

Что касается второго вопроса, "да", то будет предполагаться, что все, к чему осуществляется доступ через указатель строки, доступно только через указатель строки.

Ты мог бы бросить const и там тоже.

Наконец, если это gcc в -O2, -O3 или -Os, компилятор уже выполняет анализ псевдонимов на основе типов.Я уверен, что другие компиляторы делают это также.Это означает, что ограничение указателей на целые числа уже понято, оставляя только массивы, которые, возможно, могли бы храниться друг в друге.

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

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

Другие советы

Внешнее (второе) ограничение сообщает компилятору, что ни один из массивов указателей (A, B и out) не является псевдонимом.Внутреннее (первое) ограничение сообщает компилятору, что ни один из массивов целых чисел (на которые указывают элементы массивов указателей) не является псевдонимом.

Если вы получаете доступ как к [0][col* nrows + row], так и к[col][row], то вы нарушаете внутреннее ограничение, поэтому все может сломаться.

int **restrict утверждает только, что память, адресуемая Out, A и B не перекрываются (за исключением того, что A и B могут перекрываться, предполагая, что ваша функция не изменяет ни один из них).Это означает массивы указателей.Он ничего не утверждает о содержимом памяти, на которую указывают Out, A и B.В сноске 117 в n1124 говорится:

если идентификатор p имеет тип (int **restrict), тогда выражения указателя p и p+1 основаны на объекте ограниченного указателя, обозначенном через p, но выражения указателя *p и p[1] таковыми не являются.

По аналогии с const, Я подозреваю , что квалификация с restrict twice будет утверждать то, что вы хотите, а именно, что ни одно из значений в массиве не указывает на перекрывающуюся память.Но, читая стандарт, я не могу доказать себе, что это действительно так.Я считаю, что "Пусть D - объявление обычного идентификатора, который предоставляет средство обозначения объекта P как указателя с ограничениями на тип T", действительно означает, что для int *restrict *restrict A, тогда A[0] и A[1] являются объектами, обозначенными как ограничивающий указатель на int.Но это довольно сложный юридический язык.

Имейте в виду, я понятия не имею, действительно ли ваш компилятор будет что-то делать с этими знаниями.Очевидно, что это возможно, вопрос в том, реализовано ли это.

Так что я действительно не знаю, чего вы добились по сравнению с обычным двумерным массивом C, где вы просто выделяете rows * cols * sizeof(int), и индекс с A[cols*row + col].Тогда вам явно нужно только одно использование restrict и любой компилятор, который делает что-либо с restrict сможет переупорядочивать операции чтения из A и B при записи в Out.Без restrict, Конечно, это невозможно, поэтому, делая то, что вы делаете, вы отдаете себя на милость своего компилятора.Если он не может справиться с двойным ограничением, только с единственным случаем ограничения, то ваше двойное косвенное обращение стоило вам оптимизации.

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

является ли доступ к элементам через[0][col * nrows + row] неопределенным?

Да, если элемент изменен одним из обращений, потому что это делает A[0] псевдонимом для памяти, доступ к которой также осуществляется через[col].Это было бы прекрасно, если бы только A и B были указателями с ограничениями, но не в том случае, если A[0] и A [col] являются.

Я предполагаю, что вы не изменяете A в этой функции, так что на самом деле этот псевдоним подходит.Однако, если бы вы сделали то же самое с Out, поведение было бы неопределенным.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top