Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.
Показано с 1 по 8 из 8
  1. #1
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

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

    const-корректность

    Автор перевода: 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("I say: %s", input);
    6. return;
    7. }

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

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

    Заключение

    Надеюсь, этот урок был полезным и вы уже можете найти новые ошибки в своём коде. Может показаться, что компилятор чересчур придирчив, но эти ошибки могут вызвать серьёзные проблемы, причины которых трудно найти (например, ошибки времени выполнения "Invalid instruction").
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

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

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

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

    Статус
    Оффлайн
    Регистрация
    06.02.2017
    Сообщений
    110
    Репутация:
    14 ±
    Цитата Сообщение от NichWell Посмотреть сообщение
    Не подскажите как это зафиксить?
    1. callcmd::mdc(playerid, ""); // warning 239: literal array/string passed to a non-const parameter
    макрос инклуда командного процессора переделать нужно.
    Код:
    contact's -  (IfSkype)?(Alpano.):(vk.com/alpano)
    MyProject:
    Drift.or.Die

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

  5. #4
    Аватар для VVWVV
    Модератор

    Статус
    Оффлайн
    Регистрация
    09.07.2015
    Сообщений
    728
    Репутация:
    343 ±
    Цитата Сообщение от 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пасибо:
    NichWell (11.10.2018)
  7. #5
    Аватар для NichWell
    Пользователь

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

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

  8. #6
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,858
    Репутация:
    2337 ±
    Цитата Сообщение от 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".

    В таких случаях по-хорошему лучше выносить основную логику из команды в отдельную функцию и уже её вызывать из своего кода.
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

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

    Статус
    Оффлайн
    Регистрация
    27.01.2014
    Адрес
    Восточный Мордор
    Сообщений
    4,502
    Репутация:
    1682 ±
    Цитата Сообщение от 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);
    Связаться со мной можно в личных сообщениях этой группы

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

    Steve Pavlina

  10. #8
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,858
    Репутация:
    2337 ±
    Цитата Сообщение от 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. }
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

 

 

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

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

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

Ваши права

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