PDA

Просмотр полной версии : [Урок] const-корректность (warning 239)



Daniel_Cortez
09.10.2018, 16:26
Автор перевода: Daniel_Cortez (http://pro-pawn.ru/member.php?100-Daniel_Cortez)
Оригинальный текст: https://github.com/pawn-lang/compiler/wiki/Const-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").

Kovshevoy
10.10.2018, 20:11
Не подскажите как это зафиксить?
callcmd::mdc(playerid, ""); // warning 239: literal array/string passed to a non-const parameter

Alpano
10.10.2018, 21:24
Не подскажите как это зафиксить?
callcmd::mdc(playerid, ""); // warning 239: literal array/string passed to a non-const parameter

макрос инклуда командного процессора переделать нужно.

VVWVV
11.10.2018, 07:50
Не подскажите как это зафиксить?
callcmd::mdc(playerid, ""); // warning 239: literal array/string passed to a non-const parameter

При объявлении команды для аргумента params нужно указать const.


cmd:mdc(playerid, const params[]) {
// code..
}

Kovshevoy
11.10.2018, 19:55
При объявлении команды для аргумента params нужно указать const.


cmd:mdc(playerid, const params[]) {
// code..
}

Да, это оно, работает, спасибо <3

Daniel_Cortez
12.10.2018, 16:34
При объявлении команды для аргумента params нужно указать const.


cmd:mdc(playerid, const params[]) {
// code..
}

Это просто игнорирование проблемы вместо её исправления.
Взять вот такой пример:

CMD:buy(playerid, const params[])
{
new amount;
if (sscanf(params, "s[128] d", params, amount)) // Примем название в params, чтобы не создавать лишний массив
return SendClientMessage(playerid, -1, "Использование: /buy [название] [количество]");
// ...
return 1;
}

Этот код интересен тем, что компилятор не выдаёт ни единого варнинга, хотя массив params объявлен с const, а sscanf2 перезаписывает его содержимое.
Т.е. имеем всё ту же проблему: если вызывать обработчик команды вручную

BuyCookies(playerid)
{
return cmd::buy(playerid, "cookies 40");
}
// ...
BuyCookies(playerid);

то при первом вызове BuyCookies() команда отработает как задумано, а на втором уже сфейлится, т.к. sscanf2 перезаписывает строковый литерал и после первого вызова вместо "cookies 40" будет "cookies".

В таких случаях по-хорошему лучше выносить основную логику из команды в отдельную функцию и уже её вызывать из своего кода.

DeimoS
13.10.2018, 22:19
Это просто игнорирование проблемы вместо её исправления.
Взять вот такой пример:

CMD:buy(playerid, const params[])
{
new amount;
if (sscanf(params, "s[128] d", params, amount)) // Примем название в params, чтобы не создавать лишний массив
return SendClientMessage(playerid, -1, "Использование: /buy [название] [количество]");
// ...
return 1;
}

Этот код интересен тем, что компилятор не выдаёт ни единого варнинга, хотя массив params объявлен с const, а sscanf2 перезаписывает его содержимое.
Т.е. имеем всё ту же проблему: если вызывать обработчик команды вручную

BuyCookies(playerid)
{
return cmd::buy(playerid, "cookies 40");
}
// ...
BuyCookies(playerid);

то при первом вызове BuyCookies() команда отработает как задумано, а на втором уже сфейлится, т.к. sscanf2 перезаписывает строковый литерал и после первого вызова вместо "cookies 40" будет "cookies".

В таких случаях по-хорошему лучше выносить основную логику из команды в отдельную функцию и уже её вызывать из своего кода.

Такой вариант тоже должен сработать, если я правильно понял суть проблемы
new params[] = "cookies 40";
cmd::buy(playerid, params);

Daniel_Cortez
15.10.2018, 13:33
Такой вариант тоже должен сработать, если я правильно понял суть проблемы
new params[] = "cookies 40";
cmd::buy(playerid, params);
Сработает, но саму проблему этим не решишь. В твоём способе всё так же остаётся лишний парсинг параметров перед выполнением основного действия (т.е. покупки 40 "cookies"), ради которого вручную вызывался обработчик команды, и добавляется копирование строки "cookies 40" из секции данных каждый раз, когда создаётся локальный массив.

В посте выше под решением я имел в виду примерно такое:

enum ePRODUCT
{
PRODUCT_COOKIES,
// ...
};
BuyProduct(playerid, ePRODUCT:product, amount)
{
// ...
}

CMD:buy(playerid, params[])
{
new ePRODUCT:product, amount;
if (sscanf(params, "s[128] d", params, amount))
return SendClientMessage(playerid, -1, "Использование: /buy [название] [количество]");
// ...
return BuyProduct(playerid, product, amount);
}

BuyCookies(playerid)
{
BuyProduct(playerid, PRODUCT_COOKIES, 40); // Вместо cmd::buy(playerid, "cookies 40");
}

henrage
05.11.2018, 00:41
MysqlUpdateHouseStr(i, "hOwner", HouseInfo[i][hOwner]);

warning 239: literal array/string passed to a non-const parameter

как исправляется?

- - - Добавлено - - -


sscanf_(ip, "p.dddd", ips[0], ips[1], ips[2], ips[3]);


warning 239: literal array/string passed to a non-const parameter

Kovshevoy
05.11.2018, 00:47
MysqlUpdateHouseStr(i, "hOwner", HouseInfo[i][hOwner]);

warning 239: literal array/string passed to a non-const parameter

как исправляется?

- - - Добавлено - - -


sscanf_(ip, "p.dddd", ips[0], ips[1], ips[2], ips[3]);


warning 239: literal array/string passed to a non-const parameter

Скинь MysqlUpdateHouseStr

henrage
05.11.2018, 01:24
Скинь MysqlUpdateHouseStr


stock MysqlUpdateHouseStr(idx, field[], data[])
{
new mysql_update_string[128];
format(mysql_update_string, 128, "UPDATE "T_HOUSE" SET %s = '%s' WHERE id = %d LIMIT 1", field, data, idx);
mysql_tquery(dbHandle, mysql_update_string, "", "");
return 1;
}

DeimoS
05.11.2018, 04:49
Ты статью вообще не читал?

stock MysqlUpdateHouseStr(idx, const field[], data[])
{
new mysql_update_string[128];
format(mysql_update_string, 128, "UPDATE "T_HOUSE" SET %s = '%s' WHERE id = %d LIMIT 1", field, data, idx);
mysql_tquery(dbHandle, mysql_update_string, "", "");
return 1;
}

Duck_Leo
16.05.2021, 16:41
ShowPlayerDialog(playerid, dialogid_countries, DIALOG_STYLE_LIST, "Заголовок", "Текст", "Выбор", "Назад"); // warning 239: literal array/string passed to a non-const parameter
А у меня таких диалогов с уже готовым текстом в ShowPlayerDialog много. Выходит так, что везде надо сделать типа такого?

new header[] = "Заголовок";
new text[] = "Текст";
new button1[] = "Выбор";
new button2[] = "Назад";

tnc
16.05.2021, 16:55
Тебе нужно взять include от коммьюнити: https://github.com/pawn-lang/samp-stdlib

Duck_Leo
16.05.2021, 17:13
В нём:

native ShowPlayerDialog(playerid, dialogid, style, const caption[], const info[], const button1[], const button2[]);


https://s3.gifyu.com/images/smile_PjwVJ_VXkgMxEXlvVBE6h6ZGQCZeVCVLcbfTCa5xgdndbn.gif

Pa4enka
17.05.2021, 03:40
В нём:

native ShowPlayerDialog(playerid, dialogid, style, const caption[], const info[], const button1[], const button2[]);


https://s3.gifyu.com/images/smile_PjwVJ_VXkgMxEXlvVBE6h6ZGQCZeVCVLcbfTCa5xgdndbn.gif

И что не так?

Duck_Leo
17.05.2021, 13:14
Так. Просто не будет ли ошибки)

Pa4enka
17.05.2021, 21:38
Так. Просто не будет ли ошибки)

Эм, не будет.

Duck_Leo
18.05.2021, 13:58
Тогда не понятно почему изначально разработчики сампа не объявили официально параметры в таких ф-ях собственно как const. Может быть из-за версии компилятора Pawn (которая поставлялась в релизах), в которой нет такого warning'а?

DeimoS
18.05.2021, 21:14
Тогда не понятно почему изначально разработчики сампа не объявили официально параметры в таких ф-ях собственно как const. Может быть из-за версии компилятора Pawn (которая поставлялась в релизах), в которой нет такого warning'а?

По той же причине, по какой в SAMP полно других глупых решений и имеется куча элементарных (и не очень) багов, которые не исправляются годами.

Но я всё же предлагаю перенести подобные обсуждения либо в курилку, либо в чат - дабы оффтоп не создавать.