const-корректность (warning 239)
Автор перевода: Daniel_Cortez
Оригинальный текст: https://github.com/pawn-lang/compile...st-Correctness
В компиляторе, начиная с версии 3.10.9, появилось новое предупреждение, относящееся к правильному использованию const в объявлениях и реализациях функций.
Код:
warning 239: literal array/string passed to a non-const parameter
Поскольку данное предупреждение раскрывает ранее скрытую семантику, пользователи могут быть сбиты с толку, увидев появление этого предупреждения в своём коде в первый раз. Цель данной статьи - объяснить, что такое const-корректность, зачем она нужна и как можно писать код лучше.
Передача аргументов
Перед тем как разобраться, что означает const в объявлениях/реализациях функций, следует хорошо понимать, какими способами аргументы могут передаваться в функцию
Передача по значению
Когда вы передаёте в функцию обычные переменные, они передаются по значению:
main() {
new a; // Переменная 'a' создаётся здесь.
f(a); // 'a' передаётся в функцию 'f' по значению.
}
f(b) {
// 'b' - это копия 'a'.
// При изменении значения в 'b' значение 'a' не изменится.
}
Передача по ссылке
Также можно добавить амперсанд (знак "&") перед названием аргумента, чтобы указать компилятору, что аргумент передаётся по значению:
main() {
new a; // Переменная 'a' создаётся здесь.
f(a); // 'a' передаётся в функцию 'f' по ссылке.
// Поскольку 'f' модифицирует аргумент,
// переменная 'a' теперь равна трём.
}
f(&b) {
// 'b' - это ссылка на 'a'.
// Изменив значение 'b' мы изменим его и в 'a'.
b = 3;
}
Массивы и передача по ссылке
Массивы всегда передаются по ссылке. Причина этого не охватывается данным уроком, но вы можете почитать, почему так сделано в языке C - то же самое применимо и к Pawn.
Именно поэтому в SA-MP работают такие функции, как например GetPlayerName - они принимают ссылку на массив, который достаточно большой, чтобы вместить никнейм игрока, и записывает никнейм в массив. Ей не нужно возвращать никнейм, потому что она просто модифицирует указанный ей массив.
main() {
new a[4];
GetHi(a);
// В 'a' теперь записано "hi!" с символом конца строки.
}
GetHi(input[]) {
input[0] = 'h';
input[1] = 'i';
input[2] = '!';
input[3] = '\0';
}
В указанном выше примере мы знаем, что GetHi модифицирует input, записывая символы в каждую ячейку. Но что будет, если передать в эту функцию строковый литерал?
main() {
GetHi("123");
}
GetHi(input[]) {
input[0] = 'h';
input[1] = 'i';
input[2] = '!';
input[3] = '\0';
}
Это неопределённое поведение, которое в предыдущих версиях компилятора остаётся абсолютно незамеченным, поскольку раньше он не выдавал предупреждений о том, что скриптер допустил ошибку, передав строковый литерал в функцию, которая может попытаться изменить его.
Семантика const
И здесь на помощь приходит const. Стоит отметить, что это далеко не новый функционал компилятора; единственное, что здесь новое - это предупреждение, указывающее на функции, которые должны либо использовать const, либо модифицировать передаваемый им массив и этим массивом не должен быть строковый/массивный литерал.
Вот пример функции, которая не модифицирует передаваемый ей массив, а просто выводит его как строку:
main() {
Say("Привет!");
}
Say(input[]) {
if(input[0] == '\0') {
return;
}
printf("Я говорю: %s", input
); return;
}
Функция может выглядеть логически корректно, и так оно и есть: она проверяет, является ли строка пустой, и если да, то завершает свою работу, иначе - выводит строку и завершает работу. В логике функции нет ничего неправильного.
Тем не менее, семантика сигнатуры* функции некорректна. Некорректность можно заметить, просто прочитав сигнатуру функции:- Функция называется Say.
- Она принимает массив в качестве аргумента.
- Она может модифицировать массив.
Последний пункт и является причиной некорректности. В сигнатуре функции нет ничего, что могло бы показать, что функция не изменяет массив.
Как это исправить? С помощью const!
Say(const input[]) {
if(input[0] == '\0') {
return;
}
printf("Я говорю: %s", input
); return;
}
Теперь точно понятно, что не опасно передавать в функцию константные массивы или строковые литералы, поскольку известно, что она ни при каких (правильных) обстоятельствах не может модифицировать массив.
* Сигнатура - комбинация квалификатора функции, типов аргументов, квалификаторов аргументов и типа возвращаемого значения.
Заключение
Надеюсь, этот урок был полезным и вы уже можете найти новые ошибки в своём коде. Может показаться, что компилятор чересчур придирчив, но эти ошибки могут вызвать серьёзные проблемы, причины которых трудно найти (например, ошибки времени выполнения "Invalid instruction").