PDA

Просмотр полной версии : [Урок] Мифы о Pawn-скриптинге - #4



Daniel_Cortez
12.12.2015, 22:28
Внимание: данная тема закрыта для защиты от копирования.
Если есть какие-то вопросы, замечания или просто пожелания по поводу данного урока - оставляйте их здесь (http://pro-pawn.ru/showthread.php?12774-%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5).




Миф 4: "В командах на ZCMD/DC_CMD лучше использовать params/params[0]/params[1], чем создавать новую переменную/массив."

Статус: Опровергнут, Подтверждён.

Описание:
Похоже на предыдущий миф (http://pro-pawn.ru/showthread.php?12821) (рекомендуется к прочтению для понимания теории в данном мифе), но больше относится к командам.
Пример кода:

CMD:example(playerid, params[])
{
sscanf(params, "ud", params[0], params[1]);
// ...
}

Заблудшие обычно аргументируют использование ячеек массива params экономией памяти. На деле же этот единственный плюс незначителен и не оправдывает себя перед меньшей читаемостью и надёжностью кода.

Доказательство:
В предыдущей статье было сказано, почему не стоит использовать массивы вместо одиночных переменных.
В данной же статье будут рассмотрены дополнительные причины, почему не следует заменять все переменные на params[X] в командах.

Нет гарантии существования params[1], params[2], ...
При использовании DC_CMD, если строка params пустая, в ней будет существовать только 0-я ячейка, в которой будет символ '\0', означающий конец строки (в случае с ZCMD будет комбинация из двух ячеек {'\1', '\0'}, но это всего лишь костыль, сделанный чтобы избежать краша из-за передачи пустой строки через CallLocalFunction).
И если попытаться использовать ячейки с индексом больше 0 (params[1], params[2], etc.), возникнет ошибка доступа к неправильному участку памяти.
Проверим это утверждение на практике:

#include <a_samp>
#include <dc_cmd>

CMD:test(playerid, params[])
{
params[1] = 0; // Если строка params пустая, 1-й ячейки не будет существовать.
}

main()
{
cmd::test(0, ""); // Вызываем команду с пустой строкой.
}

При запуске с плагином crashdetect в консоль будет выведено сообщение об ошибке: "Invalid memory access".
Без crashdetect под Windows никакого эффекта не будет, но под Linux возникнет краш.
Отсюда и львиная доля крашей серверов на хостинге, которые не возникают на локальном сервере.

Запутывание кода.
Для отдельных переменных можно использовать свои имена, по которым эти переменные можно отличить друг от друга и легко понять их назначение.
Если же использовать вместо переменных ячейки массива, этим ячейкам нельзя задать отдельные названия - они различаются только по индексам, которые сложнее удерживать в голове, если в команде используется несколько ячеек params. Это может привести к путанице как у людей, которые просто читают такой код, так и у самого автора кода, который может случайно использовать не ту ячейку.


Справедливости ради, следует заметить, что при использовании 0-й ячейки массива params компилятор оптимизирует обращения к этой ячейке до 1 инструкции вместо 2 (см. предыдущую статью об использовании массивов).
Благодаря этому обращение к params[0] происходит с такой же скоростью, как и к одиночной переменной, но при этом никаких дополнительных объёмов памяти, как с одиночной переменной, выделять не нужно.
Тем не менее, это правило действует только для нулевых ячеек. Для ячеек с индексами 1, 2, 3, ... выгоднее всё же использовать локальные переменные.
К тому же в современных реалиях лучше обеспечить читаемость и надёжность кода, чем рисковать ради экономии каких-то 4 байт оперативы.
Сервера запускаются не на калькуляторах, а на хостингах, которые предоставляют десятки и даже сотни мегабайт оперативной памяти.

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

Но остаётся ещё одно применение для params: хранение массивов. Чаще всего этими массивами оказываются строки.
Рассмотрим следующий пример:

CMD:pm(playerid, params[])
{
new targetid;
sscanf(params, "us[128]", targetid, params);
if (0 == IsPlayerConnected(targetid))
return SendClientMessage(playerid, -1, "Игрок не подключен!");
return SendClientMessage(targetid, -1, params);
}

В таком случае лучше будет сохранить текст сообщения в массив params.
Во-первых, нет риска выйти за пределы массива. Если в нём помещается весь текст параметров команды, то часть текста и подавно влезет.
Во-вторых, не нужно выделять место в стеке. Также не будет никаких потерь во времени из-за инициализации всех элементов массива нулями (эти потери ничтожно малы, но они всё же есть).
В-третьих, особых проблем с понятностью кода возникнуть не должно. Если использовать params только для хранения текста, то при использовании params где-то ещё кроме sscanf (пример в коде выше: SendClientMessage(playerid, -1, params)), то станет понятно, что это какой-то текст - в данном случае это текст для личного (PM) сообщения.
Таким образом, в случае с сохранением текста из команды массив params может пригодиться, т.к. у такого подхода практически нет никаких минусов.

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



Вывод: Использование ячеек params вместо одиночных переменных чревато запутыванием кода (запоминать, что находится в params[0], а что в params[1] и params[2] мазохизму подобно).
Кроме того, размер массива params может меняться в зависимости от параметров команды, гарантировано только существование params[0]. Если попытаться сохранить что-то в другой ячейке, можно словить ошибку из-за доступа к невалидному участку памяти.
Тем не менее, есть смысл использовать массив params для сохранения в него строк из самого себя.
Пример: команда /pm [ID игрока/часть ника] [текст]

CMD:pm(playerid, params[])
{
new targetid;
sscanf(params, "us[128]", targetid, params);
if (0 == IsPlayerConnected(targetid))
return SendClientMessage(playerid, -1, "Игрок не подключен!");
return SendClientMessage(targetid, -1, params);
}




Специально для Pro-Pawn.ru (http://www.pro-pawn.ru)
Не разрешается копирование данной статьи на других ресурсах без разрешения автора.