Является ли ограничение избыточным для константного указателя?
Если мы имеем, например, f и g, определенные как:
void f(const int *restrict a, const int *restrict b, int *c){ ... }
void g(const int * a, const int * b, int *c){ ... }
- Ассемблирование f и g имеет эквивалентные тела, являются ли они одинаковыми с точки зрения вызывающего?
- В вызываемой группе мы можем сделать те же предположения о параметрах?
- Есть ли у компилятора те же возможности оптимизации?
Если restrict
избыточно, я бы ожидал, что все три ответа будут да.
В противном случае, почему бы и нет?
Не принимайте во внимание плохие методы программирования, такие как отбрасывание квалификатора const
.
Ответы
Ответ 1
В этом случае:
void f(const int *restrict a, const int *restrict b, int *c)
restrict
не является избыточным. Это означает, что компилятор может предположить, что a
и c
не являются псевдонимами. Например, если тело функции:
int d = *a;
*c = 5;
d = *a;
тогда компилятор может удалить третью строку.
Это распространяется на C11 6.7.3/7:
Эта связь, определенная в пункте 6.7.3.1 ниже, требует, чтобы все обращения к этот объект прямо или косвенно использует значение этого конкретного указателя.
который говорит, что при доступе к объекту через a
, к объекту также не допускается доступ через b
или c
.
Формальное определение можно увидеть в C11 6.7.3.1/4 (формальное определение ограничения):
Если L используется для доступа к значению объекта X, который он обозначает, и X также изменяется (любым способом), то применяются следующие требования: T не будет const-qual
Здесь T - объявленный тип, на который указывает a
, т.е. const int
, а L - *a
и X есть int
, что a
и c
указывают на.
Ответ 2
Не принимайте во внимание плохие методы программирования, такие как отбрасывание спецификатора const.
Проблема в том, что даже в стандарте C присутствие const
в указателе не является обязательным контрактом. Это всего лишь предложение программисту о том, что вызываемый не будет пытаться изменить плацдарм. Фактически, коду разрешено изменять плательщика (после кастинга), если указатель не был первоначально объявлен как объект const
.
Поэтому компиляторы не могут безопасно использовать const
: им все равно нужно проверить содержимое вызываемого абонента, чтобы убедиться, что он не лежит, если даже возможно.
Ответ 3
Имеет ли компилятор те же возможности оптимизации?
Если я правильно понимаю стандарт, то у компилятора есть большие возможности оптимизации с версией с restrict
квалифицированными указателями:
При каждом выполнении B
[некоторого блока кода] пусть L
будет любое значение l, которое имеет &L
на основе P
[ограничивающий квалифицированный указатель]. Если L
используется для доступа к значению объекта X
, который он обозначает, и X
также изменяется (любым способом), то применяются следующие требования: T
[тип P
указывает на] не должен быть const-квалифицированным. [..]
[N1570 §6.7.3.1/4, акцент мой]
Логически говоря, если объект изменен, то указатель на него не должен быть const-квалифицированным. Таким образом, если указатель имеет const-квалификацию, объект не может быть изменен (каким-либо образом).
Xmodified Tconst !Tconst Xmodified -> !Tconst
true true false false // oops, must disallow this case
true false true true
false true false true
false false true true
Таким образом, если компилятор видит T const * restrict
, то он может быть уверен, что объект "позади" этого указателя не может быть изменен в течение всего жизненного цикла этого указателя. Как таковой...
Ассемблирование f и g имеет эквивалентные тела, являются ли они одинаковыми с точки зрения вызова?
... это гораздо более надежная гарантия для вызывающего, чем просто использование T const *
.