Автор перевода: Daniel_Cortez
Оригинальный текст: https://github.com/pawn-lang/compile...st-Correctness
В компиляторе, начиная с версии 3.10.9, появилось новое предупреждение, относящееся к правильному использованию const в объявлениях и реализациях функций.
Поскольку данное предупреждение раскрывает ранее скрытую семантику, пользователи могут быть сбиты с толку, увидев появление этого предупреждения в своём коде в первый раз. Цель данной статьи - объяснить, что такое const-корректность, зачем она нужна и как можно писать код лучше.Код:warning 239: literal array/string passed to a non-const parameter
Передача аргументов
Перед тем как разобраться, что означает 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; } return; }
Функция может выглядеть логически корректно, и так оно и есть: она проверяет, является ли строка пустой, и если да, то завершает свою работу, иначе - выводит строку и завершает работу. В логике функции нет ничего неправильного.
Тем не менее, семантика сигнатуры* функции некорректна. Некорректность можно заметить, просто прочитав сигнатуру функции:
- Функция называется Say.
- Она принимает массив в качестве аргумента.
- Она может модифицировать массив.
Последний пункт и является причиной некорректности. В сигнатуре функции нет ничего, что могло бы показать, что функция не изменяет массив.
Как это исправить? С помощью const!
Say(const input[]) { if(input[0] == '\0') { return; } return; }
Теперь точно понятно, что не опасно передавать в функцию константные массивы или строковые литералы, поскольку известно, что она ни при каких (правильных) обстоятельствах не может модифицировать массив.
* Сигнатура - комбинация квалификатора функции, типов аргументов, квалификаторов аргументов и типа возвращаемого значения.
Заключение
Надеюсь, этот урок был полезным и вы уже можете найти новые ошибки в своём коде. Может показаться, что компилятор чересчур придирчив, но эти ошибки могут вызвать серьёзные проблемы, причины которых трудно найти (например, ошибки времени выполнения "Invalid instruction").