Является ли ограничение избыточным для константного указателя?

Если мы имеем, например, 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 *.