PDA

Просмотр полной версии : [Вопрос] Использование static, const



Elrmrnt-Kritik
07.03.2018, 17:17
Доброе время суток. Такой вот вопрос наметился у меня... В каких случаях лучше использовать static (либо static const) вместо new; const вместо макроса?

Ранее я static использовал только в тех случаях, когда переменная нужна лишь внутри одной функции, но при этом должна сохранять свое значение на глобальном уровне. Например:


#define MAX_PLAYERS_LENGTH 4//если вдруг изменится предел игроков, везде 4 менять не в прикол
public OnPlayerConnect(playerid)
{
static MAX_ONLINE;
if(Iter_Count(Player) > MAX_ONLINE)
MAX_ONLINE++;

new string[57+(-2+MAX_PLAYERS_LENGTH)+1];
format(string, sizeof(string), "Новый рекорд! На сервере сегодня максимум было %d игроков", MAX_ONLINE);
SendClientMessageToAll(-1, string);
}


Часто замечаю использование static и в простых участках кода, где он, казалось бы, не нужен. Назревает вопрос: когда его лучше использовать, а также в каких случаях им не стоит злоупотреблять. И аналогично хотелось бы узнать за const... Почему бы везде пишут


#define MAX_HOUSES 100

Когда ничего не мешает сделать

const MAX_HOUSES = 100;

Либо же вообще что-то подобное:


stock FuncName(arg1, arg2, const arg3) return 1;//неважно что возвращает

Long-
07.03.2018, 18:19
static - можно применять во включаемых файлах (include), область видимости этой переменной будет ограничена файлом, т.е. если в моде объявить переменную с таким же именем что и в файле, то при компиляции, не будет ошибки в виде:
error 021: symbol already defined:

const - (лат. constanta — постоянная, неизменная) позволяет создавать переменные, значение которых в дальнейшем нельзя изменить а-ля дериктива define

static const - тоже что и просто const, но опять же, позволяет ограничить область видимости в пределах файла, и избежать появления предупреждения:
warning 201: redefinition of constant/macro

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

Elrmrnt-Kritik
07.03.2018, 22:13
static - можно применять во включаемых файлах (include), область видимости этой переменной будет ограничена файлом, т.е. если в моде объявить переменную с таким же именем что и в файле, то при компиляции, не будет ошибки в виде:
error 021: symbol already defined

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



const - (лат. constanta — постоянная, неизменная) позволяет создавать переменные, значение которых в дальнейшем нельзя изменить а-ля дериктива define

Но ведь по сути и в процессе работы сервера нельзя менять значение макроса. Так что не вижу разницы между const и define.



На счет stock, не думаю что будет правильно, макрос != функция, под макрос не нужно будет выполнять такие же операции как в случае с функцией

Вопрос здесь уже был в том, когда в аргументах стоит использовать const. Здесь макросы уже не при делах.


И насчет неинтересно знать... Поверьте, мне интересная любая информация. Я хочу и пытаюсь получить новые знания. Не только на практике, но и в теории, конечно.

Long-
09.03.2018, 15:37
Но ведь по сути и в процессе работы сервера нельзя менять значение макроса. Так что не вижу разницы между const и define.

Разницы по сути и нет.


Вопрос здесь уже был в том, когда в аргументах стоит использовать const. Здесь макросы уже не при делах.

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

DeimoS
09.03.2018, 16:23
Вроде бы, на сколько я знаю это делать от читеров, собейтов и т.п.

Нет, читеры и собейты тут не причём :) Игрок никак не может влиять на данные сервера (ну кроме тех случаев, когда сервер сам позволяет это делать). Только если троян загнать на машину с сервером и через него уже химичить.

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


Касаемо вопроса автора. Вот тут (http://pro-pawn.ru/showthread.php?7762-%D0%A7%D1%82%D0%BE-%D0%BB%D1%83%D1%87%D1%88%D0%B5-new-%D0%B8%D0%BB%D0%B8-static&p=31648&viewfull=1#post31648) описано отличие static от new. Наличие const, как я уже написал выше, сигнализирует лишь о том, что переменная не должна изменяться.


Касаемо const и define. Всё же не стоит путать тёплое с мягким :) Это два совершенно разных оператора с разными назначениями, которые лишь слегка пересекаются между собой. И даже в пересечении, на самом деле, мало общего. Поясняю.

Когда мы говорим о константе, типа:

const test = 1;
это мы говорим о символьной константе, которая и правда будет идентична макросу "#define test 1"
Но если мы объявим переменную, используя оператор const

new const test = 1;
new const test[3] = {1, ...};
public const test = 1;
public const test[3] = {1, ...};
static const test = 1;
static const test[3] = {1, ...};
stock const test = 1;
stock const test[3] = {1, ...};
То тут мы уже получим именно переменную, которая будет иметь свойства, которые зависят от указанного ключевого слова + свойство const (её значение не получится изменить).


UPD: если вдруг напутал с определениями (ну, например, назвал оператором то, что не является оператором), то, думаю, меня поправят знающие люди (я любитель путать подобные вещи)

Elrmrnt-Kritik
10.03.2018, 00:46
описано отличие static от new.

Я достаточно хорошо понимаю принцип работы этих двух операторов. Вот (http://pro-pawn.ru/showthread.php?16015-stock-adminnames&p=89970&viewfull=1#post89970), например. Зачем здесь static? Точно также можно бы было использовать и new.

А еще меньше понятна ситуация, когда в начале мода что-либо объявляют через static. Ведь эта переменная и так будет создана до завершения работы сервера...

И да, один Ваш пример можно разобрать?

public const test = 1;

stock еще слышал, чтобы не было предупреждений на неиспользованную переменную, а public? Зачем он в данном случае?

DeimoS
10.03.2018, 14:21
Вот (http://pro-pawn.ru/showthread.php?16015-stock-adminnames&p=89970&viewfull=1#post89970), например. Зачем здесь static? Точно также можно бы было использовать и new.

Ну так ты читал пост Daniel Cortez, ссылку на который я кинул выше?
static там нужен, чтоб массив поместить в сегмент данных, а не в стэк. Если сделать через new, он мало того, что всякий раз будет по новой инициализироваться, так ещё и текст, который ты указал в массиве, всё равно будет помещён в сегмент данных (то бишь, ты всё так же будешь работать с сегментом данных, но, перед этим, копируя эти данные в стэк. А со static ты сразу с сегментом данных работаешь напрямую)


А еще меньше понятна ситуация, когда в начале мода что-либо объявляют через static. Ведь эта переменная и так будет создана до завершения работы сервера...

И всё же ты не читал пост Daniel Cortez =\ Там есть ответ на твой вопрос



И да, один Ваш пример можно разобрать?

public const test = 1;

stock еще слышал, чтобы не было предупреждений на неиспользованную переменную, а public? Зачем он в данном случае?

Лучше не "слушай" где попало и что попало, а загляни в официальную документацию и прочитай предназначение каждого оператора



Pawn Implementer's Guide (https://raw.githubusercontent.com/compuphase/pawn/66e67291326b193045e52e4d6bcb51663260d6c3/doc/pawn-imp.pdf)
Copyright (c) 1997–2006, ITB CompuPhase

Pawn Language Guide (https://raw.githubusercontent.com/compuphase/pawn/66e67291326b193045e52e4d6bcb51663260d6c3/doc/pawn-lang.pdf)
Copyright (c) 1997–2006, ITB CompuPhase

Elrmrnt-Kritik
10.03.2018, 21:26
Лучше не "слушай" где попало и что попало, а загляни в официальную документацию и прочитай предназначение каждого оператора

Не стал пока что читать абсолютно все, отложил это на лето, однако, прочитал за процесс компиляции и за stock, public, static. Ну, сказать честно, вопросов стало больше, но это нормально :mosking:

Так по сути stock, public и static - своеобразные маркеры? А функции по большому счету объявляются так:


funcname() return ...;

Elrmrnt-Kritik
12.03.2018, 01:46
А Ваши ответы по моей проблеме можно назад?)

DeimoS
12.03.2018, 02:19
Ждём главного администратора :) Думаю, завтра всё вернут. А если не вернут, то сами воссоздадим (только напомнишь о чём речь была (: )

Elrmrnt-Kritik
13.03.2018, 00:54
Я говорил о том, как в pawn можно использовать переменные с маркером (или есть какое-то название для таких слов, как public, stock, static?) public. Приводил свой пример с CallRemoteFunction (или CallLocalFunction, я не понимаю между ними разницы), который являлся не рабочим. Вы мне показали участок статьи с официальной документации pawn (с public связанный) и привели пример использования.

DeimoS
14.03.2018, 12:32
В CallRemoteFunction не просто так есть слово "Function" в названии :)

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

Elrmrnt-Kritik
17.03.2018, 00:54
Пошел так перебирать всю тему с начала, а чем по сути отличаются два этих кода?


const value = 8;

и


new const value = 3;


Во втором случае value помещается в память (я так полагаю сегментную память), а в первом - нет? Либо в первом случае это уже пойдет как функция?

(дело в том, что в официальной документации есть такая строка: "To declare a constant variable, insert the keyword const between the key-word that starts the variable declaration —new, static, public or stock— and the variable name". Суть в том, (как я понял) что const необходимо указывать лишь после ключевых слов, "запускающих" объявление переменной (new, stock, public, static). Ну так ведь можно и без них обойтись...).

И я так немного сравнил время работы с разными "маркерами" совместно с const. Наиболее быстро система работает с переменной, объявленной как
const value = 8;
С другими же типами немного медленнее. Очень немного. Буквально 3-4 миллисекунды. Опять же, повторюсь, при миллионе итераций.


new const value = 8;
stock const value = 8;
public const value = 8;

Nexius_Tailer
17.03.2018, 11:57
(дело в том, что в официальной документации есть такая строка: "To declare a constant variable, insert the keyword const between the key-word that starts the variable declaration —new, static, public or stock— and the variable name". Суть в том, (как я понял) что const необходимо указывать лишь после ключевых слов, "запускающих" объявление переменной (new, stock, public, static). Ну так ведь можно и без них обойтись...).
Не всегда.
Попробуй, к примеру, объявить Float массив или переменную через const, не имея при этом перед ним new/static/stock/public, т.е. вот так:

const Float:array[] = {0.0, 0.0, ...};
или вот так:

const Float:var;
...получишь ошибку.


И я так немного сравнил время работы с разными "маркерами" совместно с const. Наиболее быстро система работает с переменной, объявленной как
const value = 8;
С другими же типами немного медленнее. Очень немного. Буквально 3-4 миллисекунды. Опять же, повторюсь, при миллионе итераций.


new const value = 8;
stock const value = 8;
public const value = 8;

По скорости они скорее всего абсолютно одинаковые, а вся разница лишь в погрешностях.

DeimoS
17.03.2018, 19:17
const name = 8;
Это именованная константа. По сути, то же самое, что и макрос. Но разница всё же есть:

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

Собственно, из этого и вытекают все различия, которые я покажу на примерах:

#define some_variable pro_pawn

main()
{
new some_variable;
}

Если мы скомпилируем этот код, то в результате мы увидим такой варнинг:

warning 203: symbol is never used: "pro_pawn"
То есть, препроцессор не стал париться о том, что имя макроса совпало с именем переменной и просто переименовал её (собственно, потому что такое у макросов предназначение).

В случае с константой

const some_variable = 10;

main()
{
new some_variable;
}
мы получим предупреждение о том, что указанное имя уже занято. То бишь, константы гораздо более защищены, как минимум, в том плане, что на указанное тобой значение заменится только то, что тебе нужно.

Так же к константам применимо такое понятие, как "область видимости". То бишь, если ты создашь именованную константу в одном блоке кода, то ты уже не сможешь обратиться к ней из другого блока кода. Макросы же будут работать для всего кода, что будет после них, пока не встретит "#undef".

Отсюда и область их применения разнится.


Собственно, как уже выше заметили, именованные константы не могут быть проиндексированы (не могут быть массивами, крч).





new const name = 8;
Это уже переменная, защищённая от перезаписи (имеет все особенности переменной, но компилятор не позволит изменить её значение).

Elrmrnt-Kritik
14.04.2018, 01:27
Коль уж вопросы начали затрагивать маркеры, а значит и виды памяти, наверное, было бы неплохо переименовать эту тему... Вообще, хотел узнать за декларирование функций (forward), потому что в интернете это расписано крайне... Даже не знаю как сказать, не то что бы сложно, просто непонятно зачем оно надо.

Вот public делает переменную или функцию (дословно) общественной. Ну, будет к ней возможность обратиться из другого файла (ведь верно?). Это обращение осуществляется через CallRemoteFunction (для функций) и никак (серьезно? :sad:) (для переменных). Я бы понял, если бы forward был нужен для того, чтобы в другом файле эта функция не была неизвестной. Но ведь forward суется не в этот файл, а в мод. А значит смысл от него каков?

* под файлом выше я подразумевал какие-нибудь filterscripts.

В одной из тем шла речь про суть public-функций. Мол, они дают о себе знать в некой хеш-таблице. А что это за таблица и где можно про нее получить конкретную информацию? В официальной документации есть об этом речь. Единственное полезное, что я понял оттуда - нежелательно создание "лишних" public-функций (не нужна - не делай), мол, большая хэш-таблица ухудшается в производительности, а потому работа с public-функциями будет уже медленнее. А вот, например, тот факт, что размер хэш-таблицы ограничен и при его исчерпании нужно увеличить этот самый размер, мне уже непонятен. Каким образом его можно менять и как получить общее представление о том, чему равен максимальный размер хэш-таблицы? Или он вообще в процессе работы сервера увеличивается сам, в зависимости от частоты вызова тех или иных функций. Была информация про функции, которые должны влиять на эту таблицу, но сами функции в игровом моде не работают (якобы символ не объявлен).

SooBad
21.04.2018, 02:50
Видимо, тема еще актуальна, раз конечного ответа автор публично так и не получил.


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

По поводу forward: объявление forward для всех паблик-функций является обязательным условием. Это, своего рода, одно из правил языка.
Вдаваться в подробности не имеет смысла, однако, если любопытство не перестает угнетать - можно прочитать подробности в P.I.G.

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



Вот public делает переменную или функцию (дословно) общественной. Ну, будет к ней возможность обратиться из другого файла (ведь верно?).
Это обращение осуществляется через CallRemoteFunction (для функций) и никак (серьезно? :sad:) (для переменных). Я бы понял, если бы forward был нужен для того, чтобы в другом файле эта функция не была неизвестной. Но ведь forward суется не в этот файл, а в мод. А значит смысл от него каков?

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

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



В одной из тем шла речь про суть public-функций. Мол, они дают о себе знать в некой хеш-таблице. А что это за таблица и где можно про нее получить конкретную информацию? В официальной документации есть об этом речь. Единственное полезное, что я понял оттуда - нежелательно создание "лишних" public-функций (не нужна - не делай), мол, большая хэш-таблица ухудшается в производительности, а потому работа с public-функциями будет уже медленнее. А вот, например, тот факт, что размер хэш-таблицы ограничен и при его исчерпании нужно увеличить этот самый размер, мне уже непонятен. Каким образом его можно менять и как получить общее представление о том, чему равен максимальный размер хэш-таблицы? Или он вообще в процессе работы сервера увеличивается сам, в зависимости от частоты вызова тех или иных функций. Была информация про функции, которые должны влиять на эту таблицу, но сами функции в игровом моде не работают (якобы символ не объявлен).
Как я уже писал выше, эта таблица называется таблицей паблик-функций.
Ограничений по кол-ву созданных функций публичного типа, как таковых, не предусмотрено, ибо секреция префиксов на свой макс.размер лимитов не имеет (в отличие от стеко-кучевого блока).

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

Насчёт скорости - да, это логично. Чем больше функций, тем дольше будет осуществляться двоичный поиск в их таблице.

VVWVV
21.04.2018, 04:57
Коль уж вопросы начали затрагивать маркеры, а значит и виды памяти, наверное, было бы неплохо переименовать эту тему... Вообще, хотел узнать за декларирование функций (forward), потому что в интернете это расписано крайне... Даже не знаю как сказать, не то что бы сложно, просто непонятно зачем оно надо.

Вот public делает переменную или функцию (дословно) общественной. Ну, будет к ней возможность обратиться из другого файла (ведь верно?). Это обращение осуществляется через CallRemoteFunction (для функций) и никак (серьезно? :sad:) (для переменных). Я бы понял, если бы forward был нужен для того, чтобы в другом файле эта функция не была неизвестной. Но ведь forward суется не в этот файл, а в мод. А значит смысл от него каков?

* под файлом выше я подразумевал какие-нибудь filterscripts.

В одной из тем шла речь про суть public-функций. Мол, они дают о себе знать в некой хеш-таблице. А что это за таблица и где можно про нее получить конкретную информацию? В официальной документации есть об этом речь. Единственное полезное, что я понял оттуда - нежелательно создание "лишних" public-функций (не нужна - не делай), мол, большая хэш-таблица ухудшается в производительности, а потому работа с public-функциями будет уже медленнее. А вот, например, тот факт, что размер хэш-таблицы ограничен и при его исчерпании нужно увеличить этот самый размер, мне уже непонятен. Каким образом его можно менять и как получить общее представление о том, чему равен максимальный размер хэш-таблицы? Или он вообще в процессе работы сервера увеличивается сам, в зависимости от частоты вызова тех или иных функций. Была информация про функции, которые должны влиять на эту таблицу, но сами функции в игровом моде не работают (якобы символ не объявлен).

Очень кратко:


Спецификатор forward необходим для декларирования всех функций. Тем не менее в функциях со спецификатором stock немного другая схема работы: он требует forward-декларацию только тогда, когда функция возвращает значение с определённым тегом.

Функции, объявленные с помощью спецификатора public являются публичным. Их названия хранятся в скомпилированном AMX файле (названия других функций там не хранится).

Это не хеш-таблица, а обычная отсортированная таблица (это весьма очевидно, ведь используется бинарный поиск). При использовании большого количества таких функций произойдёт "раздувание" файла, то есть его размера. Если бы была таблица, то доступ к элементу занимал O(1), а не O(log n) при бинарном поиске.

Вообще можно поменять размер блока (реаллацировать), хранящего байт-код, но это будет странно, ведь это вовсе не нужно. Тем более, придётся перехватить функцию загрузки AMX-скриптов.




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

Странно, как тогда устроен YSI? Да, там хаки, но работает ж... =)

Elrmrnt-Kritik
21.04.2018, 21:21
Очень рад, что тема не оказалась незамеченной, спасибо большое вам.


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

Ну, я имею в виду такое:

public variable;

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

И, если честно, из двух ответов я так и не уловил суть forward'a. Я знаю когда его нужно делать и где, я не знаю какую именно роль он выполняет...

SooBad
21.04.2018, 21:39
И, если честно, из двух ответов я так и не уловил суть forward'a. Я знаю когда его нужно делать и где, я не знаю какую именно роль он выполняет...
Вообще, декларирование осуществляется, чтобы узнать необходимый размер памяти при создании определенного объекта (переменной, функции и т.п.). В Pawn же, объявление декларирования для функций осуществляется вручную, в отличие от переменных.

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

VVWVV
21.04.2018, 23:29
Очень рад, что тема не оказалась незамеченной, спасибо большое вам.



Ну, я имею в виду такое:

public variable;

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

И, если честно, из двух ответов я так и не уловил суть forward'a. Я знаю когда его нужно делать и где, я не знаю какую именно роль он выполняет...

Если переменная объявлена с помощью public, то это не значит, что она должна быть в общей таблице функций. Для таких переменных есть специальная таблица.

Elrmrnt-Kritik
22.04.2018, 01:00
Если переменная объявлена с помощью public, то это не значит, что она должна быть в общей таблице функций. Для таких переменных есть специальная таблица.

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

Daniel_Cortez
22.04.2018, 01:04
Так если есть специальная таблица, как же ей оперировать можно? Данные из нее с других файлов извлекать (переменные с маркером public).
Либо из плагинов, либо из скриптов через эксплуатацию уязвимостей в ВМ (чтение/запись за пределами секции данных). Стандартными методами со стороны скриптов таблица с экспортируемыми переменными недоступна. То же самое касается и таблицы экспортируемых функций.

Elrmrnt-Kritik
15.06.2018, 01:22
После каждого прочтения возникают разные вопросы :) А оператор new выделяет в стэке место под переменную? Или у него какая-то другая основная задача?

VVWVV
15.06.2018, 02:35
После каждого прочтения возникают разные вопросы :) А оператор new выделяет в стэке место под переменную? Или у него какая-то другая основная задача?

Да, выделяет, если он в функции. Если же нет, то он использует секцию данных.