Да, как сказал Wallyk, компилятор может удалить бесполезные операции в этом случае.
Однако вы должны помнить, что при указании сигнатуры функции что-то теряется при переводе из вашего проблемного домена в C. Рассмотрим следующую функцию:
void transform(const double *xyz, // Source point
double *txyz, // Transformed points
const double *m, // 4x3 transformation matrix
int n) // Number of points to transform
{
for (int i=0; i<n; i++)
{
txyz[0] = xyz[0]*m[0] + xyz[1]*m[3] + xyz[2]*m[6] + m[9];
txyz[1] = xyz[0]*m[1] + xyz[1]*m[4] + xyz[2]*m[7] + m[10];
txyz[2] = xyz[0]*m[2] + xyz[1]*m[5] + xyz[2]*m[8] + m[11];
txyz += 3; xyz += 3;
}
}
Я думаю, что намерениеЯсно, однако, что компилятор должен быть параноиком и учитывать, что сгенерированный код должен вести себя точно так, как описано в семантике C, даже в случаях, которые, конечно, не являются частью первоначальной проблемы преобразования массива точек, таких как:
txyz
и xyz
указывают на один и тот же адрес памяти или могут указывать на смежные двойные значения в памяти m
указывает внутри области txyz
Это означает, что для вышеуказанной функции компилятор C вынужден предполагать, что после каждой записи в txyz
любой из xyz
или m
может измениться, и поэтому эти значения не могут быть загружены в свободном порядке.Получающийся код, следовательно, не сможет использовать преимущества параллельного выполнения, например, вычислений координат дерева, даже если процессор позволит это сделать.
Этот случай наложения имен был настолько распространенным, что C99 ввелконкретное ключевое слово, чтобы сказать компилятору, что ничего такого странного не было.Размещение ключевого слова restict
в объявлении txyz
и mat
убеждает компилятор в том, что указанная память недоступна другими способами, и тогда компилятору разрешается генерировать лучший код.
Однакоэто «параноидальное» поведение все еще необходимо для всех операций, чтобы гарантировать корректность, и поэтому, например, если вы пишете код, такой как
char *s = malloc(...);
char *t = malloc(...);
... use s and t ...
, компилятор не может знать, что две области памяти не будут перекрываться илиПроще говоря, в языке C нет способа определить сигнатуру, чтобы выразить концепцию, согласно которой возвращаемые значения из malloc
"не перекрываются".Это означает, что параноидальный компилятор будет думать, что в последующем коде любая запись во что-то, обозначенное s
, может перезаписать данные, обозначенные t
(даже если вы не получаете размер, переданный malloc
, я имею в виду; -)).
В вашем примере даже параноидальный компилятор может предположить, что
- никто не будет знать адрес локальной переменной, если только он не получен в качестве параметра
- между чтением и вычислением сложения не выполняется неизвестный внешний код
если обе эти точки потеряны, то компилятор должен подумать о странных возможностях;например
int a = malloc(sizeof(int));
*a = 1;
printf("Hello, world.\n");
// Here *a could have been changed
Эта безумная мысль необходима, потому что malloc
знает адрес a
;так что он мог бы передать эту информацию printf
, чтобы после печати строка могла использовать этот адрес для изменения содержимого местоположения.Это кажется совершенно абсурдным, и, возможно, объявление библиотечной функции может содержать какой-то особый непереносимый прием, но в целом это необходимо для корректности (представьте, malloc
и printf
- две пользовательские функции вместо библиотечных).
Что все это означает?Это да, в вашем случае компилятору разрешено оптимизировать, но эту возможность очень легко удалить;например,
inline int Func1 (int* a)
{
printf("pointed value is %i\n", *a);
return *a + 1;
}
int main ()
{
int v = GetIntFromUserInput(); // Assume input value is non-determinable.
printf("Address of v is %p\n", &v);
return Func1(&v);
}
- это простая вариация вашего кода, но в этом случае компилятор не может избежать предположения, что второй вызов printf
мог изменить указанную память, даже если он передал только указанное значение ине адрес (потому что первый вызов printf
был передан адресу, и поэтому компилятор должен предположить, что потенциально эта функция могла бы сохранить адрес, чтобы использовать его позже для изменения переменной).
Очень распространенныйзаблуждение в C и C ++ заключается в том, что либеральное использование ключевого слова const
с указателями или (в C ++) ссылками поможет оптимизатору генерировать лучший код.Это полностью неверно:
- В объявлении
const char *s
ничего не сказано о том, что указанный символ будет постоянным;просто сказано, что изменение указанного символа с помощью этого указателя является ошибкой.Другими словами, const
в данном случае просто означает, что указатель «только для чтения», но не говорит о том, что, например, другие указатели могут быть использованы для изменения той же памяти, на которую указывает s
. - В C (и C ++) допустимо «отбрасывать» константу от указателя (или ссылки) на константу.Таким образом, параноидальный компилятор должен предполагать, что даже функция была передана только
const int *
, функция может хранить этот указатель и позже может использовать его для изменения памяти, на которую указывает.
Ключевое слово const
с указателями (и ссылками на C ++) предназначен только для помощи программисту, чтобы избежать непреднамеренного написания использования указателя, который считался использованным только для чтения.После выполнения этой проверки оптимизатор просто забывает об этом ключевом слове const
, поскольку оно не влияет на семантику языка.
Иногда вы можете найти еще одно глупое использование ключевого слова const
с параметрамиэто говорит о том, что значение параметра не может быть изменено;например void foo(const int x)
.Этот вид использования не имеет реального философского значения для сигнатуры и просто немного раздражает реализацию вызываемой функции: параметр является копией значения, и вызывающей стороне должно быть все равно, будет ли вызываемая функция изменять эту копию.или нет ... вызываемая функция все еще может сделать копию параметра и изменить эту копию, так что ничего не получится.
Напомним ... когда компилятор увидит
void foo(const int * const x);
по-прежнему должен предполагать, что foo потенциально сохранит копию переданного указателя и может использовать эту копию для изменения памяти, на которую указывает x
, немедленно или позже, когда вы вызываете любую другую неизвестную функцию.
Этот уровеньпаранойи требуется из-за того, как определяется языковая семантика.
Очень важно понять эту проблему "псевдонимов" (могут быть разные способы изменить одну и ту же область памяти с возможностью записи), особенно в C ++, гдеесть общий анти-паттерн передачи константных ссылок внесколько значений, даже когда логически функция должна принимать значение.См. этот ответ , если вы также используете C ++.
Все эти причины, по которым при работе с указателями или ссылками, оптимизатор имеет гораздо меньше свободы, чем с локальными копиями.