2016-12-12 10 views
2

Хотя есть много примеров [1][2][3], которые адресуют, как работает ключевое слово restrict, я не совсем уверен, является ли ограниченное отношение транзитивным по указателям, на которые он указывает. Например, следующий код объявляет структуру, которая содержит целое число и указатель на целое число.Является ли ограничитель C ограниченным?

typedef struct container_s { 
    int x; 
    int *i; 
} container_s; 


int bar(container_s *c, int *i) { 
    int* tmp = c->i; 
    *tmp = 5;  
    *i = 4; 
    return *tmp; 
} 

int main(){ 
    return 0; 
} 

ли компилятор нужен дополнительный load инструкции для последнего доступа tmp (возвращенное значение), потому что он не может сделать вывод, что *i и *tmp не псевдоним?

Если да, то это новое определение исправить, что загружается?

int bar(container_s *c, int* restrict i) { ... } 

РЕДАКТИРОВАТЬ

Этот случай int bar(container_s *c, int * restrict i) { ... } снимает нагрузку экстракт, когда я производить LLVM IR (clang -S -O3 -emit-llvm). Тем не менее, я не понимаю, почему в ближайшие две модификации не удалить эту конечную нагрузку, когда:

  1. Я обновляю определение функции (это restrict транзитивно считается для c->i?) По адресу:

    int bar(container_s * restrict c, int *i) { ... } 
    
  2. Я обновить структуру, как показано ниже (Почему не компилятор делать вывод, что нет необходимости в дополнительной нагрузке?):

    typedef struct container_s { 
        int x; 
        int * restrict i; 
    } container_s; 
    
    int bar(container_s *c, int *i) { ... } 
    
+1

'вернуть * TMP;' Функция должна возвращать пустое (: = не возвращает значение) – joop

+4

вы имели в виду, чтобы иметь 'container_s * ограничить c', так как вы спрашиваете, является ли оно транзитивно, но ваш первоначальный у кода нет 'ограничений' где-нибудь? – Arkku

+0

Как компиляторы будут * в общем * превращать ваш код C в машинный код, не является предметом ответственности. 'ограничивать' говорит (не) сглаживание с помощью указателей, а способ, если таковой имеется, в котором компилятор обрабатывает обращения с помощью указателя с ограничениями, отличного от доступа через неквалифицированный указатель, является аспектом реализации этого компилятора , –

ответ

2

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

ли компилятор нужен дополнительный load инструкции для последнего доступа tmp (возвращенное значение), потому что он не может сделать вывод, что *i и *tmp не псевдоним?

компилятор действительно не может с уверенностью заключить, что *i и *tmp не псевдоним в исходном коде, как вы наглядно продемонстрировал.Из этого не следует, что компилятору необходимо исправить команду load, подразумеваемую абстрактной машинной семантикой оператора *, но необходимо позаботиться о том, чтобы решить проблему с псевдонимом как-то.


Если да, то [ограничить-квалификационный параметр i] исправить эту нагрузку?

Добавление restrict -qualification к параметру i в определении функции помещает следующее дополнительное требование о поведении программы (полученной из текста C2011, 6.7.3.1/4): во время каждого выполнения bar(), потому i является (тривиальным) на основеi и *i используются для доступа к объекту оно обозначает, и что обозначенный объект изменяется во время выполнения bar() (через *i по крайней мере), все остальное именующее выражение используется для доступа к объекту, назначенному *i также имеет свой адрес, основанный на i.

*tmp имеет доступ, и его адрес, tmp, не основан на i. Следовательно, если i == tmp (то есть, если при некотором вызове i == c->i), то программа не соответствует. В этом случае его поведение не определено. Компилятор волен испускают код, который принимает на себя программа соответствует установленным требованиям, так, в частности, в restrict -qualified случае он может испускать код, который предполагает как то, что заявление

*i = 4; 

не изменяет *tmp, и что заявление

*tmp = 5; 

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

В частности, если компилятор выбирает для обработки возможности наложения спектров в исходном коде, выполняя, возможно, избыточную нагрузку *tmp, то в restrict -qualified версии может выбрать оптимизацию, исключив, что load. Однако полученный машинный код ни в коем случае не является , а должен различаться между двумя случаями. То есть вы не можете, в общем, полагаться на компилятор, чтобы использовать все доступные ему оптимизации.

Update:

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

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

Переводчика может свободно игнорировать любые или все последствия наложения спектров от использования restrict.

(C2011, 6.7.3.1/6)

С учетом сказанного, на вопросы.

  1. В этом коде варианте *tmp является именующим, адрес которого основан на restrict -qualified указателя c. Объект, который он обозначает, получает доступ через это значение в пределах области действия функции, а также изменен в пределах этой области (через *tmp, поэтому компилятор, безусловно, может ее увидеть). Адрес *i не основан на c, поэтому компилятор может предположить, что *i не является псевдонимом *tmp, так же, как в исходном вопросе.

  2. Этот случай отличается. Хотя разрешено ограничивать-квалифицировать элементы структуры, restrict имеет эффект только тогда, когда он квалифицирует обычный идентификатор (C2011, 6.7.3.1/1), имена структурных членов которого не являются (C2011, 6.2.3). В этом случае restrict не имеет никакого эффекта, и для обеспечения соответствующего поведения компилятор должен учитывать возможность того, что c->i и *i*tmp) являются псевдонимами.

+0

Я получаю вашу точку, которая подтверждается при создании LLVM IR. Не могли бы вы дать дополнительные комментарии сейчас, когда я обновил вопрос по пунктам 1 и 2? –

+0

@KikoFernandez, я обновил свой ответ с ответами на ваши последующие вопросы. –

2

«будет этот новый заголовок исправить эту нагрузку?», -> Нет restrict относится к i, и доступ к его полям:

... требует, чтобы все обращения к этому использованию объекта, прямо или косвенно, значение этого конкретного указателя ... C11 §6.7.3 8

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

#include<stdio.h> 

typedef struct container_s { 
    int x; 
    int *i; 
} container_s; 


int bar(container_s * c, int* restrict i) { 
    int* tmp = c->i; 
    *tmp = 5; 
    *i = 4; 
    return *tmp; 
} 

int main(void) { 
    int i = 42; 
    container_s s = { 1, &i }; 

    printf("%d\n", bar(&s, &i)); 
    printf("%d\n", i); 
    printf("%d\n", *(s.i)); 
} 

Выход

4 
4 
4 
+0

Да, компилятор не должен излучать другой код в случае с ограниченным доступом, чем в случае без квалификации, но я думаю, что формальное определение 'ограничивать' (6.7 .3.1) позволяет сделать это в случае ОП. –

+0

@JohnBollinger Согласен с первой частью [комментарий] (http://stackoverflow.com/questions/41105620/is-the-c-restrict-qualifier-transitive/41106295?noredirect=1#comment69420826_41106295). Неясно, «разрешает ли это делать это в случае OP». Вы имеете в виду OP void bar (container_s * c, int * i) 'или' void bar (container_s * c, int * restrict i) '? – chux

+0

Я имею в виду, что в ограниченном случае компилятор допускается считать без проверки того, что '* i' не псевдоним' * tmp'. Таким образом, выход вашей программы может быть '5',' 4', '4'. Или на самом деле это может быть что-то еще, потому что программа не соответствует. –