Возвращение строк и массивов

Описание: Уроки по скриптингу
Модератор: SJplayer

Layk M
Автор темы, Прапорщик
Прапорщик
Аватара
Layk M
Автор темы, Прапорщик
Прапорщик
Сообщения: 170
Зарегистрирован: 4 июня 2013
С нами: 10 лет 9 месяцев

#1 Layk » 15 сентября 2019, 19:43

Хотя функции в Pawn обычно возвращают только простые значения размера ячейки, язык позволяет (даже нативным) функциям возвращать массивы и, следовательно, также строки. Однако механизм, который позволяет это сделать, немного сложнее и может не подходить для некоторых случаев использования.

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

Через выходной параметр
Все собственные функции SA-MP генерируют строку через стандартный неконстантный параметр массива. Поскольку массивы передаются по ссылке (то есть функция получает доступ к фактической переменной, а не только к ее внутреннему значению), нативная функция может легко хранить любые данные внутри:

Код: Выделить всё

new name[MAX_PLAYER_NAME + 1];
GetPlayerName(playerid, name, sizeof name);


Обычно, другой параметр используется наряду с параметром массива, чтобы указать длину массива. В Pawn нет простого способа получить длину массива непосредственно во время выполнения, и поэтому компилятор должен предоставлять длину при необходимости (оператор sizeof). Для строк в массиве должна быть выделена дополнительная ячейка для хранения нулевого символа, указывающего конец строки (hence the + 1)/(отсюда + 1).

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

Возврат массива напрямую
Если вы хотите создать массив фиксированного размера, вы можете вернуть его непосредственно из функции:

Код: Выделить всё

forward [MAX_PLAYER_NAME + 1]PlayerName(playerid);
stock PlayerName(playerid)
{
    new name[MAX_PLAYER_NAME + 1];
    GetPlayerName(playerid, name, sizeof name);
    return name;
}


Форвардное объявление является необязательным, но оно полезно, поскольку позволяет вам помнить о важности длины массива.

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

На самом деле функция выглядит так:

Код: Выделить всё

stock PlayerName(playerid, output[MAX_PLAYER_NAME + 1])
{
    new name[MAX_PLAYER_NAME + 1];
    GetPlayerName(playerid, name, sizeof name);
    output = name;
}


Как видите, возвращение строки - это просто удобный синтаксический ярлык для дополнительного выходного параметра.

Однако это происходит за счет незначительного снижения производительности и ошибок в некоторых случаях.
Давайте начнем с вопросов:

Код: Выделить всё

stock Select(index, arg[], arg2[])
{
    if(index == 0) return arg;
    return arg2;
}


Эта простая функция, кажется, возвращает один из аргументов, но поскольку они имеют неопределенные размеры (представленные как 0), компилятор считает, что эта функция возвращает массив нулевого размера и фактически не выделяет никакого дополнительного пространства для строки. Вы не можете возвращать строки (или массивы), которые имеют неопределенную длину, и компилятор иногда не информирует вас об этом.

Код: Выделить всё

forward [4]Func1();
stock Func1()
{
    new str[] = "abc";
    return str;
}

forward [4]Func2();
stock Func2()
{
    return Func1();
}


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

Код: Выделить всё

stock Func(...)
{
    new str[] = "abc";
    return str;
}


Дополнительный выходной аргумент помещается в конце всех аргументов, даже после переменных. Однако компилятор не может правильно получить адрес возврата в этом случае и предполагает, что его позиция постоянна.

Во всех этих случаях возвращать строку очень плохо, так как код обычно компилируется нормально, и проблема становится очевидной только во время выполнения.

С возвратом массивов также связаны небольшие затраты производительности: output = name; всегда происходит, если функция реализована в Pawn, и поэтому массив копируется хотя бы один раз.
Посмотрите на этот код:

Код: Выделить всё

new name[MAX_PLAYER_NAME + 1]; // = PlayerName(playerid); не работает, прямое назначение не поддерживается
name = PlayerName(playerid);


Компилятор снова выделяет дополнительное пространство для строки, возвращаемой из PlayerName (всегда в куче), прежде чем он переместит его в name. Поэтому массив должен быть скопирован без необходимости дважды, прежде чем его можно будет использовать.

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

Код: Выделить всё

format(string, sizeof string, "Your name is %s.", PlayerName(playerid));


Его предполагаемое использование возвращаемых массивов - в качестве временных аргументов для других функций. Во всех остальных случаях использование нормального выходного параметра безопаснее и быстрее. В этом случае никакого дополнительного копирования на сайте звонящего не происходит.

Сам PlayerName может быть «исправлен», чтобы возвращать строку напрямую без ненужного копирования через встроенную сборку:

Код: Выделить всё

forward [MAX_PLAYER_NAME + 1]PlayerName(playerid);
stock PlayerName(playerid)
{
    #assert MAX_PLAYER_NAME + 1 == 25
    #emit PUSH.C 25 // параметр размера GetPlayerName
    #emit PUSH.S 16 // секретный возвращаемый параметр PlayerNameпо адресу 16 (&playerid + 4)
    #emit PUSH.S playerid // равно  12
    #emit PUSH.C 12 // количество байтов, переданных функции (4 * 3 аргумента)
    #emit SYSREQ.C GetPlayerName //  вызов функции
    #emit STACK 16 //чистка аргументов из стека
    #emit RETN
}


Таким образом, PlayerName не выполняет дополнительного копирования, передавая секретный обратный адрес непосредственно в GetPlayerName.

PawnPlus strings
Динамические строки в PawnPlus предлагают гибкость и удобство обычных значений, поскольку они передаются как ссылки.

Код: Выделить всё

stock String:Func()
{
    return str_new("abc");
}


Строка может быть возвращена из функций, передана другим функциям или даже собственным функциям, проверена или изменена, и все это без какого-либо дополнительного копирования.

Постоянные строки/Constant strings
Все предыдущие методы подходят для строк, которые создаются во время выполнения, но для константных строк я бы рекомендовал не использовать ни одну из них. Вместо этого используйте макросы.

Код: Выделить всё

new const _tips[][] = {
    "Tip one",
    "Tip two",
    "Tip three",
    "Tip four"
};
#define RandomTip() (_tips[random(sizeof _tips)])  


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


Вернуться в «Уроки»

Кто сейчас на форуме (по активности за 5 минут)

Сейчас этот раздел просматривают: 6 гостей