Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.
Страница 1 из 2 1 2 ПоследняяПоследняя
Показано с 1 по 10 из 20
  1. #1
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±

    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 в объявлениях/реализациях функций, следует хорошо понимать, какими способами аргументы могут передаваться в функцию

    Передача по значению

    Когда вы передаёте в функцию обычные переменные, они передаются по значению:
    1. main() {
    2. new a; // Переменная 'a' создаётся здесь.
    3. f(a); // 'a' передаётся в функцию 'f' по значению.
    4. }
    5.  
    6. f(b) {
    7. // 'b' - это копия 'a'.
    8. // При изменении значения в 'b' значение 'a' не изменится.
    9. }

    Передача по ссылке

    Также можно добавить амперсанд (знак "&") перед названием аргумента, чтобы указать компилятору, что аргумент передаётся по значению:
    1. main() {
    2. new a; // Переменная 'a' создаётся здесь.
    3. f(a); // 'a' передаётся в функцию 'f' по ссылке.
    4. // Поскольку 'f' модифицирует аргумент,
    5. // переменная 'a' теперь равна трём.
    6. }
    7.  
    8. f(&b) {
    9. // 'b' - это ссылка на 'a'.
    10. // Изменив значение 'b' мы изменим его и в 'a'.
    11. b = 3;
    12. }

    Массивы и передача по ссылке

    Массивы всегда передаются по ссылке. Причина этого не охватывается данным уроком, но вы можете почитать, почему так сделано в языке C - то же самое применимо и к Pawn.

    Именно поэтому в SA-MP работают такие функции, как например GetPlayerName - они принимают ссылку на массив, который достаточно большой, чтобы вместить никнейм игрока, и записывает никнейм в массив. Ей не нужно возвращать никнейм, потому что она просто модифицирует указанный ей массив.
    1. main() {
    2. new a[4];
    3. GetHi(a);
    4. // В 'a' теперь записано "hi!" с символом конца строки.
    5. }
    6.  
    7. GetHi(input[]) {
    8. input[0] = 'h';
    9. input[1] = 'i';
    10. input[2] = '!';
    11. input[3] = '\0';
    12. }

    В указанном выше примере мы знаем, что GetHi модифицирует input, записывая символы в каждую ячейку. Но что будет, если передать в эту функцию строковый литерал?
    1. main() {
    2. GetHi("123");
    3. }
    4.  
    5. GetHi(input[]) {
    6. input[0] = 'h';
    7. input[1] = 'i';
    8. input[2] = '!';
    9. input[3] = '\0';
    10. }

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

    Семантика const

    И здесь на помощь приходит const. Стоит отметить, что это далеко не новый функционал компилятора; единственное, что здесь новое - это предупреждение, указывающее на функции, которые должны либо использовать const, либо модифицировать передаваемый им массив и этим массивом не должен быть строковый/массивный литерал.

    Вот пример функции, которая не модифицирует передаваемый ей массив, а просто выводит его как строку:
    1. main() {
    2. Say("Привет!");
    3. }
    4.  
    5. Say(input[]) {
    6. if(input[0] == '\0') {
    7. return;
    8. }
    9. printf("Я говорю: %s", input);
    10. return;
    11. }

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

    Тем не менее, семантика сигнатуры* функции некорректна. Некорректность можно заметить, просто прочитав сигнатуру функции:
    • Функция называется Say.
    • Она принимает массив в качестве аргумента.
    • Она может модифицировать массив.

    Последний пункт и является причиной некорректности. В сигнатуре функции нет ничего, что могло бы показать, что функция не изменяет массив.

    Как это исправить? С помощью const!
    1. Say(const input[]) {
    2. if(input[0] == '\0') {
    3. return;
    4. }
    5. printf("Я говорю: %s", input);
    6. return;
    7. }

    Теперь точно понятно, что не опасно передавать в функцию константные массивы или строковые литералы, поскольку известно, что она ни при каких (правильных) обстоятельствах не может модифицировать массив.

    * Сигнатура - комбинация квалификатора функции, типов аргументов, квалификаторов аргументов и типа возвращаемого значения.

    Заключение

    Надеюсь, этот урок был полезным и вы уже можете найти новые ошибки в своём коде. Может показаться, что компилятор чересчур придирчив, но эти ошибки могут вызвать серьёзные проблемы, причины которых трудно найти (например, ошибки времени выполнения "Invalid instruction").
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  2. 5 пользователя(ей) сказали cпасибо:
    bullplex (04.11.2018) DiceLine (09.07.2019) Kovshevoy (10.10.2018) Osetin (10.10.2018) VVWVV (09.10.2018)
  3. #2
    Аватар для Kovshevoy
    Пользователь

    Статус
    Оффлайн
    Регистрация
    11.07.2015
    Сообщений
    190
    Репутация:
    25 ±
    Не подскажите как это зафиксить?
    1. callcmd::mdc(playerid, ""); // warning 239: literal array/string passed to a non-const parameter

  4. #3
    Аватар для Alpano
    Пользователь

    Статус
    Оффлайн
    Регистрация
    06.02.2017
    Сообщений
    123
    Репутация:
    16 ±
    Цитата Сообщение от NichWell Посмотреть сообщение
    Не подскажите как это зафиксить?
    1. callcmd::mdc(playerid, ""); // warning 239: literal array/string passed to a non-const parameter
    макрос инклуда командного процессора переделать нужно.
    MyProject:
    DriftEmpire©

    Ленивые всё делают быстро, чтобы поскорее избавиться от работы.
    И делают качественно, чтобы потом не переделывать.

  5. #4
    Аватар для VVWVV
    ?

    Статус
    Оффлайн
    Регистрация
    09.07.2015
    Сообщений
    731
    Репутация:
    353 ±
    Цитата Сообщение от NichWell Посмотреть сообщение
    Не подскажите как это зафиксить?
    1. callcmd::mdc(playerid, ""); // warning 239: literal array/string passed to a non-const parameter
    При объявлении команды для аргумента params нужно указать const.

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

  6. Пользователь сказал cпасибо:
    Kovshevoy (11.10.2018)
  7. #5
    Аватар для Kovshevoy
    Пользователь

    Статус
    Оффлайн
    Регистрация
    11.07.2015
    Сообщений
    190
    Репутация:
    25 ±
    Цитата Сообщение от VVWVV Посмотреть сообщение
    При объявлении команды для аргумента params нужно указать const.

    1. cmd:mdc(playerid, const params[]) {
    2. // code..
    3. }
    Да, это оно, работает, спасибо <3

  8. #6
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Цитата Сообщение от VVWVV Посмотреть сообщение
    При объявлении команды для аргумента params нужно указать const.

    1. cmd:mdc(playerid, const params[]) {
    2. // code..
    3. }
    Это просто игнорирование проблемы вместо её исправления.
    Взять вот такой пример:
    1. CMD:buy(playerid, const params[])
    2. {
    3. new amount;
    4. if (sscanf(params, "s[128] d", params, amount)) // Примем название в params, чтобы не создавать лишний массив
    5. return SendClientMessage(playerid, -1, "Использование: /buy [название] [количество]");
    6. // ...
    7. return 1;
    8. }

    Этот код интересен тем, что компилятор не выдаёт ни единого варнинга, хотя массив params объявлен с const, а sscanf2 перезаписывает его содержимое.
    Т.е. имеем всё ту же проблему: если вызывать обработчик команды вручную
    1. BuyCookies(playerid)
    2. {
    3. return cmd::buy(playerid, "cookies 40");
    4. }
    5. // ...
    6. BuyCookies(playerid);

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

    В таких случаях по-хорошему лучше выносить основную логику из команды в отдельную функцию и уже её вызывать из своего кода.
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  9. #7
    Аватар для DeimoS
    Модератор?

    Статус
    Оффлайн
    Регистрация
    27.01.2014
    Адрес
    Восточный Мордор
    Сообщений
    5,588
    Репутация:
    1984 ±
    Цитата Сообщение от Daniel_Cortez Посмотреть сообщение
    Это просто игнорирование проблемы вместо её исправления.
    Взять вот такой пример:
    1. CMD:buy(playerid, const params[])
    2. {
    3. new amount;
    4. if (sscanf(params, "s[128] d", params, amount)) // Примем название в params, чтобы не создавать лишний массив
    5. return SendClientMessage(playerid, -1, "Использование: /buy [название] [количество]");
    6. // ...
    7. return 1;
    8. }

    Этот код интересен тем, что компилятор не выдаёт ни единого варнинга, хотя массив params объявлен с const, а sscanf2 перезаписывает его содержимое.
    Т.е. имеем всё ту же проблему: если вызывать обработчик команды вручную
    1. BuyCookies(playerid)
    2. {
    3. return cmd::buy(playerid, "cookies 40");
    4. }
    5. // ...
    6. BuyCookies(playerid);

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

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

    Широко известно, что идеи стоят 0.8333 цента каждая (исходя из рыночной цены 10 центов за дюжину).
    Великих идей полно, на них нет спроса.
    Воплощение идеи в законченную игру требует долгой работы,
    таланта, терпения и креативности, не говоря уж о затратах денег, времени и ресурсов.
    Предложить идею просто, воплотить – вот в чём проблема

    Steve Pavlina

  10. #8
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Цитата Сообщение от DeimoS Посмотреть сообщение
    Такой вариант тоже должен сработать, если я правильно понял суть проблемы
    1. new params[] = "cookies 40";
    2. cmd::buy(playerid, params);
    Сработает, но саму проблему этим не решишь. В твоём способе всё так же остаётся лишний парсинг параметров перед выполнением основного действия (т.е. покупки 40 "cookies"), ради которого вручную вызывался обработчик команды, и добавляется копирование строки "cookies 40" из секции данных каждый раз, когда создаётся локальный массив.

    В посте выше под решением я имел в виду примерно такое:
    1. enum ePRODUCT
    2. {
    3. PRODUCT_COOKIES,
    4. // ...
    5. };
    6. BuyProduct(playerid, ePRODUCT:product, amount)
    7. {
    8. // ...
    9. }
    10.  
    11. CMD:buy(playerid, params[])
    12. {
    13. new ePRODUCT:product, amount;
    14. if (sscanf(params, "s[128] d", params, amount))
    15. return SendClientMessage(playerid, -1, "Использование: /buy [название] [количество]");
    16. // ...
    17. return BuyProduct(playerid, product, amount);
    18. }
    19.  
    20. BuyCookies(playerid)
    21. {
    22. BuyProduct(playerid, PRODUCT_COOKIES, 40); // Вместо cmd::buy(playerid, "cookies 40");
    23. }
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  11. Пользователь сказал cпасибо:
    bullplex (04.11.2018)
  12. #9
    Аватар для henrage
    Пользователь

    Статус
    Оффлайн
    Регистрация
    05.11.2018
    Сообщений
    2
    Репутация:
    0 ±
    PHP код:
    MysqlUpdateHouseStr(i"hOwner",  HouseInfo[i][hOwner]); 
    PHP код:
    warning 239literal array/string passed to a non-const parameter 
    как исправляется?

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

    PHP код:
    sscanf_(ip"p.dddd"ips[0], ips[1], ips[2], ips[3]); 
    PHP код:
    warning 239literal array/string passed to a non-const parameter 

  13. #10
    Аватар для Kovshevoy
    Пользователь

    Статус
    Оффлайн
    Регистрация
    11.07.2015
    Сообщений
    190
    Репутация:
    25 ±
    Цитата Сообщение от henrage Посмотреть сообщение
    PHP код:
    MysqlUpdateHouseStr(i"hOwner",  HouseInfo[i][hOwner]); 
    PHP код:
    warning 239literal array/string passed to a non-const parameter 
    как исправляется?

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

    PHP код:
    sscanf_(ip"p.dddd"ips[0], ips[1], ips[2], ips[3]); 
    PHP код:
    warning 239literal array/string passed to a non-const parameter 
    Скинь MysqlUpdateHouseStr

 

 
Страница 1 из 2 1 2 ПоследняяПоследняя

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •