Kailo
Участник
- Сообщения
- 194
- Реакции
- 896
Уроки не заслуживающие отдельных тем под них.
Содержание:
1. Порядок байтов
2. Нарушение размеров массива для неявной модификации данных
3. Присвоение char массиву начального значения
4. Преобразование типа массивов на примере не обычного получения адреса из hostip
5. Структура массивов SourcePawn
6. Блок кода
7. Условие с инструкциями
8. Конкатенация строк
9. Опасность проверки на интервал (двойное сравнение)
10. Именованная передача параметров
11. Битовые флаги
1. Порядок байтов
Память разбита на байтовые ячейки. В памяти данные переменных размером более одного байта записываются в порядке определенным языком программирования. У C++ и SourcePawn этот порядок от младшего к старшему (англ. little-endian). Пару примеров для лучшего понимания:
Если хотите знать больше или не поняли, читайте Википедия:Порядок байтов
2. Нарушение размеров массива для неявной модификации данных
Все мы знаем, что запрещено превышать размеры массивов, иначе получим ошибку "out of bounds" (дословно, за пределами). Ну а сейчас я расскажу, как за них выбраться, не получая ошибку и что это дает.
Во-первых надо знать как происходит передача параметров при вызове функции. А конкретно как передаются массивы.
Массивы передаются только по адресу образуя ссылку (Если вы этого не понимаете, можете дальше особо не читать этот урок, позже, если скажут что нужно, напишу отдельный урок).
А так как ссылка это по сути всего лишь адрес без размера, то проверки на размер не существует.
Пример:
Что же это нам позволяет делать, спросите вы. Учитывая тот факт, что переменные расположены последовательно в памяти, согласно их объявлению, мы можем менять значения переменных "не явно". Т.е. присваивания значения переменной в программе вы не найдете. Вот вам пример:
Как видите здесь не явно изменяется значение константы :blink:
Это лишь один из многих способов использовать данную возможность. Вы можете придумать своё применение этому. Но вы должны понимать, что вы делаете и зачем, иначе работать не будет.
P.S. Именно поэтому вы передаете размер строки (символьного массива) при вызове функций, которые при модификации содержимого могут увеличивать кол-во символов.
3. Присвоение char массиву начального значения
Урок содержит ошибки!
Этот урок, ответ на вопрос, как задать начальное значение массива типа char конструкцией вида
Объяснение: (если вам не интересно, то сразу смотрите в конец урока)
Что бы понять как это сделать и не получать ошибок, надо знать несколько аспектов программирования на SourcePawn.
1) Конструкция типа { ... } возвращает объект типа int.
2) Константа типа 'f', имеет тип int, а не char как вы могли подумать.
3) Каждая ячейка конструкции { ... } имеет размер 4 байта и тип соответствующий типу введённых данных
4) Нужно понимать 1й урок этой темы о порядке байтов данных в памяти.
5) Система ячеек (англ. cell), подразумевает что данные должны быть записаны блоками по 4 байта (Поэтому char переменные кратны 4 (Могли видеть при декомпиляции, что размеры "строк" увеличиваются до кратного 4), и всё по этой же причине переменная типа bool занимает 4 байта, а не 1 бит).
6) Пустым ячейкам автоматически дается нулевое значение
Часто, если мы делаем так, ...:
('\0' это, если кто-то не знает, эквивалент нулевого символа, соответственно равное \x00 или 0).
... многие ожидают что получат строку вида "53", или же, если расписывать байтовые ячейки памяти, то:
Но если вы все уже поняли, то вам ясно, что согласно пункту 3 и 6, память выглядит следующим образом:
А согласно пунктам 1, 2 и 3 о типах данных, получим предупреждения о том что массиву типа char присваиваются данные типа int, и еще 3 ошибки (на каждую ячейку) о несовместимости типов (ожидался char, а дан int).
Получается, надо слепить 4 байта в одну ячейку, но вспоминая о порядке байтов, их следует перевернуть:
Т.е. для строки "53", что есть '5', '3', '\0', '0', а если перевернуть '\0', '\0', '3', '5'. Переводим буквы в hex по таблице ANSI/UTF-8 и получаем 00 00 33 35, сливаем в единое число 3335 и либо пишем его в hex как 0x3335 или переводим в DEC 13109
Получим:
Далее исправляем типы данных и получаем:
Это абсолютно правильный код, который не вызывает ошибок, можете сами проверить, попытавшить вывести строку в чат или консоль, и получите "53".
Но этот процесс немного утомительный и сложный, поэтому я облегчил задачу используя макросы:
Все математические операции будут произведены во время компиляции, и в итоге превратится в конечное число.
Теперь, мы можем делать следующие:
Итог:
Что же это нам даёт:
1) Расширяет инструментарий языка;
2) Позволяет записывать нулевой символ в любом месте строки или строку без нулевого символа;
3) Следуя из 2го, позволяет обманывать декомпиляторы, т.к. тот считывает строку до первого нулевого символа. Запишем строку где первый символ '\0', а остальные нормальные, и потом или обращаемся к строке через a[1] или изменяем 1й символ a[0] = 'a', а декомпилятор, либо выдаст ошибку, либо подумает что строка пустая.
P.S. Мне кажется, что такого даже на AlliedModers форуме нет — эксклюзив.
4. Преобразование типа массивов на примере не обычного получения адреса из hostip
В основе лежит возможность поменять тип массива при передачи его как параметра, с помощью конструкции view_as<>().
Квар hostip хранит адрес сервера. Так как, адрес это 4 восьмибитных числа (0-255), его записали как 32 бита int числа. Наша цель представить 4 байта числа, как 4х байтовый массив.
Сделать это есть несколько способов, но после нескольких попыток, я решил, что нашел самый оптимальный способ для конкретной задачи по получению адреса (см. пример 1).
В функцию передается один и тот же массив два раза, но с разными типами (для упрощения я сделал макрос; иначе придется добавлять дополнительную функцию, см. пример 2).
Присваиваю первым 4м битам значение равное адресу.
Далее не забывая о порядке байтов, формирую строку адреса; плюс ноль нужен для получения значения по указанному адресу, иначе он передает адрес char массива со смещением. Идеальный способ был бы разыменовать, но у SourcePawn нет такой операции.
Пример 1
Пример 2
Остальная часть тестовой программы
5. Структура массивов SourcePawn
С SP 1.11 не актуально, т.к. в ней обновили структуру массивов!
Одно размерный массив устроен по обычному. А вот о формате много размерных я и собираюсь рассказать.
К примеру скажем вы захотели массив myarray[3][4]. Какой будет его суммарный размер? Правильно! 15! (Что? Не угадали?)
Ну 12 байтов понятно, на сами данные. Куда же еще 3 и какую информацию они хранят?
Для того чтобы пример был нагляднее постановим, что
Если представить, что по тому же адресу что и myarray хранится одно размерный массив, его содержание было бы
Первые три числа обозначают смещение в байтах от своего адреса, до начала соответствующего "подмассива" (нижней размерности)!
В памяти массив хранится адресом. Обозначим адресс нашего массива за n.Когда мы делаем myarray[2] или myarray[var], где var - переменная (предположим тоже 2 равна), происходит вычисление адреса "подмассива" по следующем алгоритму:
P.S. С увеличением размерности матрешка разрастается.
6. Блок кода
Представляет собой набор инструкций объединенных фигурными скобками.
Все переменные объявленные внутри блока перестают существовать после конца блока.
Применяется редко и скорее всего в целях оптимизации.
Т.е. в случае когда переменная используется не на протяжении всей функции, а только её части. С целью уменьшить кол-во одновременно занимаемого кол-ва памяти, в конце блока переменная прекратит существовать.
Пример объявления одноименных переменных в разных блоках.
Так же, используя старый синтаксис можно сделать неявное присваивание.
И так же это можно использовать для обмана декомпиляторов, т.к. они редко поддерживают отслеживание блоков. К примеру такой вот код. Декомпилятор не поставит скобки блока, и компилятор выдаст ошибки описанные в комментариях.
7. Условие с инструкциями
В SourcePawn разрешено выполнять инструкции внутри if условия, отделяя их запятыми от самого условия
Инструкций может быть сколько угодно много.
Данная конструкция полезна когда требуется выполнить промежуточные вычисления для следующего условия.
К примеру мы ищем объекты класса prop_dynamic:
В свою очередь можно записать как
Пример для уроков 6 и 7 из личной практики.
Команда в которой происходит передача steamid и нам надо проверить его корректность.
8. Конкатенация строк
У компилятора SourcePawn есть оператор конкатенации для объединения константных строк.
В процессе компиляции строки будут объединены как единая новая строка. В роли оператора выступает троеточие.
Данный синтаксис будет удобным в случае выноса каких-либо частей в define.
К примеру версия плагина или имя таблицы.
9. Опасность проверки на интервал (двойное сравнение)
С какой-то версии SP более не актуально, т.к результат выражения в серидине начали сохранять, а не вычислять 2 раза!
Я говорю о коде вида
Такое часто используется для проверки индекса игрока
Но на самом деле это преобразуется в (могли видеть, если использовали Lysis)
Теперь поговорим что может быть в этом плохого
1) Обработка результатов сравнений совместно (Если из увиденного кода вам не понятно, читайте спойлер)
2) У кого-то может сложится мнение, что он сравнивает полученное значение, получая его только 1 раз (у тех кто еще не читал пункт 1). Но как вы можете видеть, значение получается дважды. А в случае функции, будет два вызова функции.
10. Именованная передача параметров
Еще один редкий приём синтаксиса, унаследованный от Pawn.
Сначала напомню еще одну истину, вдруг кто не знает.
В SourcePawn доступны параметры вызова по-умолчанию. Это значит что при вызове вы можете их не указывать, и они буду установлены на значения по умолчанию, которые указаны в описании функци. К примеру:
При этом есть специальный символ "_" (нижнее подчеркивание), чтобы пропустить параметр при передаче и оставить его со значением по-умолчанию.
К примеру мы хотим указать param3 как 9 и не трогать при этом другие параметры, мы можем сделать:
Значения параметров будут 1, 3, 9, 7.
При малом кол-ве параметров это не проблема пропустить 3, 4 параметра, но вот вам к примеру функция EmitSoundToAll:
А нам как назло понадобилось изменить soundtime на 5.0...
Но нас есть решение, вместо того что-бы указывать 10 пропусков, мы можем указать значение какого именно параметра мы передаем по его имени:
Думаю синтаксис понятен: точка, имя параметра, знак равенство и само значение для передачи.
На их использование действуют следующие ограничения:
Все не именованные параметры должны передаваться только до именованных.
Соответственно и нельзя передавать не именованные параметры после именованных.
Порядок передачи именованных не важен и не как не зависит от порядка объявления, т.е. следующий код будет вполне корректным:
11. Битовые флаги
Меня спросили: Что значит эта конструкция?
Это битовые флаги.
Числовая переменная представляет собой записанное определенным образом 4х байтное число.
Байт - это единица размера данных которой может оперировать процессор.
Бит - это единица хранения информации в комьютерах.
Бит может быть в двух значениях, 0 или 1.
1 байт = 8 бит для современных процессоров.
Наше число 4 байта = 32 бита. Вместо того чтобы считать информацию одним 4-х байтным числом,
мы начнём считать что тут 32 однобитных числа (32 флага, они могут быть 1 или 0 каждый).
Дальше вступает алгебра логики (булева алгебра) Булева алгебра — Википедия
Начнём с того что превратим краткую запись в полную
Если кратко можно записать
Оператор
Я запишу число в битовом виде, а чтобы было понятнее вначале буду писать префикс 0b.
Хоть наши переменные 32 бита, в примерах я буду использовать лишь 8 бит.
Проще говоря, если оба числа 1, то результат 1, иначе 0.
И это надо сделать с отдельными битами двух чисел (поразрядно).
Вернёмся к нашим флагам. У нас по факту 32 независимых друг от друга переменных.
Но вот проблема, они все внутри одной переменной и вы не можете поменять так просто конкретную.
FCVAR_NOTIFY представляет собой число, где 1 стоит только в одном бите, который отвечает за указанный флаг.
К примеру:
Теперь сделаем наши преобразования
Мы вычисляем новое значение флагов в зависимости от старого.
Представим что данный код выполняется и подставим в правой части числа в значениях переменных которые бы они имели в момент выполнения.
Инверсия битов имеет больший приоритет и выполнится первой.
Теперь выполним логическое И, и можно заметить, что второе значение, это все 1, кроме того бита, где находится нужный нам бит флага.
А то как работает логическое умножение даёт нам понять:
Т.е. где 1 останется тот же флаг что и был, а где 0 флаг станет 0.
А вот бывшее значение
Как можно увидеть, измениться лишь значение 4го бита, он станет равен 0.
Т.е. Эта запись логически обозначает: "Поставить флагу FCVAR_NOTIFY значение 0"
Для проверки наличия флага используется логичкское И с маской флага и сравнивается с 0
А для установки флага (присвоения 1) - логическое ИЛИ с маской флага
Содержание:
1. Порядок байтов
2. Нарушение размеров массива для неявной модификации данных
3. Присвоение char массиву начального значения
4. Преобразование типа массивов на примере не обычного получения адреса из hostip
5. Структура массивов SourcePawn
6. Блок кода
7. Условие с инструкциями
8. Конкатенация строк
9. Опасность проверки на интервал (двойное сравнение)
10. Именованная передача параметров
11. Битовые флаги
1. Порядок байтов
Память разбита на байтовые ячейки. В памяти данные переменных размером более одного байта записываются в порядке определенным языком программирования. У C++ и SourcePawn этот порядок от младшего к старшему (англ. little-endian). Пару примеров для лучшего понимания:
C-подобный:
332967832 // Число в десятичной системе счисления (DEC)
13 D8 AF 98 // Оно же в шестнадцетиричной системе (HEX), каждому байту будет соответствовать по 2 символа, поэтому сразу разделим число
98 AF D8 13 // Но если считать участок памяти побайтового, где хранится число, то в HEX получим
Рассмотрим еще один случай с числом где старших разрядов нет
512 // DEC
200 // HEX
00 00 02 00 // Дополняем HEX до 8 чисел нулями
00 02 00 00 // Вид в памяти
2. Нарушение размеров массива для неявной модификации данных
Все мы знаем, что запрещено превышать размеры массивов, иначе получим ошибку "out of bounds" (дословно, за пределами). Ну а сейчас я расскажу, как за них выбраться, не получая ошибку и что это дает.
Во-первых надо знать как происходит передача параметров при вызове функции. А конкретно как передаются массивы.
Массивы передаются только по адресу образуя ссылку (Если вы этого не понимаете, можете дальше особо не читать этот урок, позже, если скажут что нужно, напишу отдельный урок).
А так как ссылка это по сути всего лишь адрес без размера, то проверки на размер не существует.
Пример:
PHP:
int g_array[3];
int g_var = 4;
public void OnPluginStart()
{
func(g_array);
}
void func(int[] array)
{
PrintToSever("%i", array[3]); // Сработает, выведет значение g_var
PrintToSever("%i", g_array[3]); // Выдаст ошибку
}
Что же это нам позволяет делать, спросите вы. Учитывая тот факт, что переменные расположены последовательно в памяти, согласно их объявлению, мы можем менять значения переменных "не явно". Т.е. присваивания значения переменной в программе вы не найдете. Вот вам пример:
PHP:
int g_state[MAXPALYERS + 1];
bool g_cankill = false;
const int g_worktype = 3;
public void OnPluginStart()
{
g_cankill = !g_cankill;
RegConsoleCmd("sm_cmd", Cmd);
func(g_state);
}
void func(int[] array)
{
array[g_worktype + 64] = 2;
}
public Action Cmd(int client, int args)
{
if (g_worktype != 2) return Plugin_Handled;
// Do something...
return Plugin_Handled;
}
Это лишь один из многих способов использовать данную возможность. Вы можете придумать своё применение этому. Но вы должны понимать, что вы делаете и зачем, иначе работать не будет.
P.S. Именно поэтому вы передаете размер строки (символьного массива) при вызове функций, которые при модификации содержимого могут увеличивать кол-во символов.
3. Присвоение char массиву начального значения
Урок содержит ошибки!
Этот урок, ответ на вопрос, как задать начальное значение массива типа char конструкцией вида
C-подобный:
char a[] = { ... };
Что бы понять как это сделать и не получать ошибок, надо знать несколько аспектов программирования на SourcePawn.
1) Конструкция типа { ... } возвращает объект типа int.
2) Константа типа 'f', имеет тип int, а не char как вы могли подумать.
3) Каждая ячейка конструкции { ... } имеет размер 4 байта и тип соответствующий типу введённых данных
4) Нужно понимать 1й урок этой темы о порядке байтов данных в памяти.
5) Система ячеек (англ. cell), подразумевает что данные должны быть записаны блоками по 4 байта (Поэтому char переменные кратны 4 (Могли видеть при декомпиляции, что размеры "строк" увеличиваются до кратного 4), и всё по этой же причине переменная типа bool занимает 4 байта, а не 1 бит).
6) Пустым ячейкам автоматически дается нулевое значение
Часто, если мы делаем так, ...:
C-подобный:
char a[] = {'5', '3', '\0'};
... многие ожидают что получат строку вида "53", или же, если расписывать байтовые ячейки памяти, то:
C-подобный:
'5', '3', '\0', '\0' (4 символа по причине 5го пункта)
C-подобный:
'5', '\0', '\0', '\0', '3', '\0', '\0', '\0', '\0', '\0', '\0', '\0'
Получается, надо слепить 4 байта в одну ячейку, но вспоминая о порядке байтов, их следует перевернуть:
Т.е. для строки "53", что есть '5', '3', '\0', '0', а если перевернуть '\0', '\0', '3', '5'. Переводим буквы в hex по таблице ANSI/UTF-8 и получаем 00 00 33 35, сливаем в единое число 3335 и либо пишем его в hex как 0x3335 или переводим в DEC 13109
Получим:
C-подобный:
char a[] = {0x3335};
или char a[] = {13109};
C-подобный:
char a[] = view_as<char>({view_as<char>(13109)});
Но этот процесс немного утомительный и сложный, поэтому я облегчил задачу используя макросы:
C-подобный:
#define charblock(%1,%2,%3,%4) view_as<char>(%4 << 24 | %3 << 16 | %2 << 8 | %1)
Теперь, мы можем делать следующие:
C-подобный:
char a[] = view_as<char>({charblock('5', '3', '\0', '\0')});
Итог:
C-подобный:
#define charblock(%1,%2,%3,%4) view_as<char>(%4 << 24 | %3 << 16 | %2 << 8 | %1)
// Запишем строку "Hello, world!"
char a[] = view_as<char>({charblock('H', 'e', 'l', 'l'), charblock('o', ',', ' ', 'w'), charblock('o', 'r', 'l', 'd'), charblock('\0', '\0', '\0', '\0')});
Что же это нам даёт:
1) Расширяет инструментарий языка;
2) Позволяет записывать нулевой символ в любом месте строки или строку без нулевого символа;
3) Следуя из 2го, позволяет обманывать декомпиляторы, т.к. тот считывает строку до первого нулевого символа. Запишем строку где первый символ '\0', а остальные нормальные, и потом или обращаемся к строке через a[1] или изменяем 1й символ a[0] = 'a', а декомпилятор, либо выдаст ошибку, либо подумает что строка пустая.
P.S. Мне кажется, что такого даже на AlliedModers форуме нет — эксклюзив.
4. Преобразование типа массивов на примере не обычного получения адреса из hostip
В основе лежит возможность поменять тип массива при передачи его как параметра, с помощью конструкции view_as<>().
Квар hostip хранит адрес сервера. Так как, адрес это 4 восьмибитных числа (0-255), его записали как 32 бита int числа. Наша цель представить 4 байта числа, как 4х байтовый массив.
Сделать это есть несколько способов, но после нескольких попыток, я решил, что нашел самый оптимальный способ для конкретной задачи по получению адреса (см. пример 1).
В функцию передается один и тот же массив два раза, но с разными типами (для упрощения я сделал макрос; иначе придется добавлять дополнительную функцию, см. пример 2).
Присваиваю первым 4м битам значение равное адресу.
Далее не забывая о порядке байтов, формирую строку адреса; плюс ноль нужен для получения значения по указанному адресу, иначе он передает адрес char массива со смещением. Идеальный способ был бы разыменовать, но у SourcePawn нет такой операции.
Пример 1
PHP:
#define GetServerIp(%1,%2) GetServerIpFunc(view_as<int>(%1), %1, %2)
// Размер буфера >= 16
void GetServerIpFunc(int[] array, char[] buffer, int maxlength)
{
array[0] = cvar_hostip.IntValue;
FormatEx(buffer, maxlength, "%i.%i.%i.%i", buffer[3] + 0, buffer[2] + 0, buffer[1] + 0, buffer[0] + 0);
}
Пример 2
PHP:
// Размер буфера >= 16
void GetServerIp(char[] buffer, int maxlength)
{
GetServerIpHelper(view_as<int>(buffer));
FormatEx(buffer, maxlength, "%i.%i.%i.%i", buffer[3] + 0, buffer[2] + 0, buffer[1] + 0, buffer[0] + 0);
}
void GetServerIpHelper(int[] array)
{
array[0] = cvar_hostip.IntValue;
}
Остальная часть тестовой программы
PHP:
// Тут располагается функция
ConVar cvar_hostip;
public void OnPluginStart()
{
RegConsoleCmd("sm_ip", Cmd_IP);
cvar_hostip = FindConVar("hostip");
}
public Action Cmd_IP(int client, int args)
{
char ipaddress[16];
GetServerIp(ipaddress, sizeof(ipaddress));
ReplyToCommand(client, "%s", ipaddress);
return Plugin_Handled;
}
5. Структура массивов SourcePawn
С SP 1.11 не актуально, т.к. в ней обновили структуру массивов!
Одно размерный массив устроен по обычному. А вот о формате много размерных я и собираюсь рассказать.
К примеру скажем вы захотели массив myarray[3][4]. Какой будет его суммарный размер? Правильно! 15! (Что? Не угадали?)
Ну 12 байтов понятно, на сами данные. Куда же еще 3 и какую информацию они хранят?
Для того чтобы пример был нагляднее постановим, что
C-подобный:
myarray[3][4] = {
{50, 100, 150, 200},
{-1, -2, -3, -4},
{1001, 1002, 1003, 1004}
};
C-подобный:
mybro[15] = {12, 24, 36, 50, 100, 150, 200, -1, -2, -3, -4, 1001, 1002, 1003, 1004};
В памяти массив хранится адресом. Обозначим адресс нашего массива за n.Когда мы делаем myarray[2] или myarray[var], где var - переменная (предположим тоже 2 равна), происходит вычисление адреса "подмассива" по следующем алгоритму:
- Если это переменная, а массив имеет фиксированное значение, то сначала проверяется, не превышает ли он размеры массива. Потом значение индекса преобразовывается в смещение адреса, путем умножения на 4 (размер ячейки).
b = n + var * 4;
Если это константа, то она уже была проверена при компиляции и преобразована в смещение.
b = n + const; - Далее происходит получение значения по адресу и прибавление к текущему адресу b.
Получаем адрес i-го "подмасива"
c = load(b) + b;
где load это получение значения по адресу b (так же можно записать для любителей C++ как c = *b + b) - Далее происходит проверка следующей размерности, если переменная. И либо прибавления смещения (если тип массива char, то просто прибавляется значение без домножения на размер ячейки) к c, если нужен адрес или еще последующая загрузка значения.
val = load(c + var2 * 4);
C-подобный:
var = 1;
var2 = 2;
val = myarray[var][var2]; // Получим -3
// Эквивалентно
val = mybro[var + (mybro[var] / 4) + var2]; // Получим -3
6. Блок кода
Представляет собой набор инструкций объединенных фигурными скобками.
PHP:
public void OnPluginStart()
{
{
// Блок кода
}
}
Применяется редко и скорее всего в целях оптимизации.
Т.е. в случае когда переменная используется не на протяжении всей функции, а только её части. С целью уменьшить кол-во одновременно занимаемого кол-ва памяти, в конце блока переменная прекратит существовать.
Пример объявления одноименных переменных в разных блоках.
PHP:
public void OnPluginStart()
{
{
int a = 3;
PrintToServer("%i", a);
}
{
int a = 5;
PrintToServer("%i", a);
}
int a = 7;
PrintToServer("%i", a);
}
PHP:
public OnPluginStart()
{
{
decl String:HS[10];
strcopy(HS, sizeof(HS), "Trick me!");
}
decl String:str[10];
PrintToServer(str); // Выведет "Trick me!", т.к. str занимает тот же участок памяти что ранее HS
}
PHP:
{
int buffer = 5;
PrintToServer("Good Day Sir!", buffer);
}
char buffer[16]; // Ругаемся что переменная с таким именем уже объявлена
GetCmdArgString(buffer, sizeof(buffer)) // Ругаемся на не совпадение типов, т.к. думаем что buffer int. Ну и не можем выполнить sizeof не для массива.
7. Условие с инструкциями
В SourcePawn разрешено выполнять инструкции внутри if условия, отделяя их запятыми от самого условия
PHP:
if (инструкция, условие)
{
}
Данная конструкция полезна когда требуется выполнить промежуточные вычисления для следующего условия.
К примеру мы ищем объекты класса prop_dynamic:
PHP:
char classname[32];
for (int entity = 65, max = GetMaxEntities(); entity < max; entity++)
if (IsValidEntity(entity)) {
GetEntityClassname(entity, classname, sizeof(classname));
if (StrEqual(classname, "prop_dynamic"))
PrintToServer("prop_dynamic(%i)", entity);
}
PHP:
char classname[32];
for (int entity = 65, max = GetMaxEntities(); entity < max; entity++)
if (IsValidEntity(entity) && (GetEntityClassname(entity, classname, sizeof(classname)), StrEqual(classname, "prop_dynamic")))
PrintToServer("prop_dynamic(%i)", entity);
Пример для уроков 6 и 7 из личной практики.
Команда в которой происходит передача steamid и нам надо проверить его корректность.
PHP:
public Action Cmd_MyCmd(int client, int args)
{
char auth[24];
// Getting auth and validate
{
int reason;
if ((reason = 1, GetCmdArg(1, auth, sizeof(auth)) < 11)
|| (reason = 2, strncmp(auth, "STEAM_1:", 8))
|| (reason = 3, auth[8] != '0' && auth[8] != '1')
|| (reason = 4, auth[9] != ':')
|| (reason = 5, !IsStringNumeric(auth[10])))
{
ReplyToCommand(client, "Not valid steamid (%i). Need: STEAM_1:X:Y", reason);
return Plugin_Handled;
}
}
// Do stuff
return Plugin_Handled;
}
8. Конкатенация строк
У компилятора SourcePawn есть оператор конкатенации для объединения константных строк.
В процессе компиляции строки будут объединены как единая новая строка. В роли оператора выступает троеточие.
PHP:
"строка1" ... "строка2" // после компиляции на этом месте будет "строка1строка2"
К примеру версия плагина или имя таблицы.
PHP:
#define PLUGIN_VERSION
public Plugin myinfo = {
author = PLUGIN_VERSION,
};
// Где-то в коде, сообщение при входе игрока на сервер
PrintToChat(client, "Сервер работает под управлением Мой_Плагин версии " ... PLUGIN_VERSION ... ".");
PHP:
#define DBTABLE "sm_myvipplugin"
// Где-то в коде, запрос к БД
g_DB.Query(SQLQueryCallback, "SELECT steamid, group, time FROM " ... DBTABLE);
9. Опасность проверки на интервал (двойное сравнение)
С какой-то версии SP более не актуально, т.к результат выражения в серидине начали сохранять, а не вычислять 2 раза!
Я говорю о коде вида
PHP:
if (0 < var < 10)
PHP:
if (0 < client <= MaxClients)
PHP:
if (MaxClients >= client & 0 < client)
1) Обработка результатов сравнений совместно (Если из увиденного кода вам не понятно, читайте спойлер)
Рассмотрим пример
Как вы знаете GetClientTeam выкинет ошибку, если игрок не в игре, но данный код не кидает ошибку, потому что, если первая часть логического И false, то он и не станет проверять вторую, а сразу перейдет к коду после конца if блока. Это распространяется так же и на логическое ИЛИ (||), если оператор вернет true, то программа не станет проверять/выполнять последующие условия, а сразу перейдет к коду внутри блока if.
В случае же проверки на интервал, бинарное И. Программа сначала посчитает оба условия и только потом будет проверять его в if.
Т.е. если у нас client > MaxClients программа все равно проверит если client > 0.
PHP:
if (IsClientInGame(client) && GetClientTeam(client) > CS_TEAM_SPECTATOR)
В случае же проверки на интервал, бинарное И. Программа сначала посчитает оба условия и только потом будет проверять его в if.
Т.е. если у нас client > MaxClients программа все равно проверит если client > 0.
PHP:
if (0 < MyFunc() < 10)
// на самом деле будет
if (10 > MyFunc() & 0 < MyFunc())
// И более лучше будет сделать
int value = MyFunc();
if (value > 0 && value < 10)
10. Именованная передача параметров
Еще один редкий приём синтаксиса, унаследованный от Pawn.
Сначала напомню еще одну истину, вдруг кто не знает.
В SourcePawn доступны параметры вызова по-умолчанию. Это значит что при вызове вы можете их не указывать, и они буду установлены на значения по умолчанию, которые указаны в описании функци. К примеру:
PHP:
// Имея функцию
void MyFuncAlpha(int param1 = 1, int param2 = 3, int param3 = 3, int param4 = 7);
// Мы можем вызвать её как
MyFuncAlpha();
// и тогда параметры будут переданы с значениями 1, 3, 3 и 7.
К примеру мы хотим указать param3 как 9 и не трогать при этом другие параметры, мы можем сделать:
PHP:
MyFuncAlpha(_, _, 9);
При малом кол-ве параметров это не проблема пропустить 3, 4 параметра, но вот вам к примеру функция EmitSoundToAll:
PHP:
/**
* Wrapper to emit sound to all clients.
*
* @param sample Sound file name relative to the "sounds" folder.
* @param entity Entity to emit from.
* @param channel Channel to emit with.
* @param level Sound level.
* @param flags Sound flags.
* @param volume Sound volume.
* @param pitch Sound pitch.
* @param speakerentity Unknown.
* @param origin Sound origin.
* @param dir Sound direction.
* @param updatePos Unknown (updates positions?)
* @param soundtime Alternate time to play sound for.
* @error Invalid client index.
*/
stock void EmitSoundToAll(const char[] sample,
int entity = SOUND_FROM_PLAYER,
int channel = SNDCHAN_AUTO,
int level = SNDLEVEL_NORMAL,
int flags = SND_NOFLAGS,
float volume = SNDVOL_NORMAL,
int pitch = SNDPITCH_NORMAL,
int speakerentity = -1,
const float origin[3] = NULL_VECTOR,
const float dir[3] = NULL_VECTOR,
bool updatePos = true,
float soundtime = 0.0);
PHP:
EmitSoundToAll("sample.wav", _, _, _, _, _, _, _, _, _, _, 5.0); // НЕ БРО!
PHP:
EmitSoundToAll("sample.wav", .soundtime = 5.0); // БРО!
На их использование действуют следующие ограничения:
Все не именованные параметры должны передаваться только до именованных.
Соответственно и нельзя передавать не именованные параметры после именованных.
Порядок передачи именованных не важен и не как не зависит от порядка объявления, т.е. следующий код будет вполне корректным:
PHP:
EmitSoundToAll(.soundtime = 5.0, .updatePos = false, .sample = g_AlarmSound);
11. Битовые флаги
Меня спросили: Что значит эта конструкция?
C-подобный:
Cvar_FF = FindConVar("mp_friendlyfire");
Cvar_FF.Flags &= ~FCVAR_NOTIFY;
Числовая переменная представляет собой записанное определенным образом 4х байтное число.
Байт - это единица размера данных которой может оперировать процессор.
Бит - это единица хранения информации в комьютерах.
Бит может быть в двух значениях, 0 или 1.
1 байт = 8 бит для современных процессоров.
Наше число 4 байта = 32 бита. Вместо того чтобы считать информацию одним 4-х байтным числом,
мы начнём считать что тут 32 однобитных числа (32 флага, они могут быть 1 или 0 каждый).
Дальше вступает алгебра логики (булева алгебра) Булева алгебра — Википедия
Начнём с того что превратим краткую запись в полную
Если кратко можно записать
Cvar_FF.Flags &= ~FCVAR_NOTIFY;
, в полном виде Cvar_FF.Flags = Cvar_FF.Flags & ~FCVAR_NOTIFY;
Оператор
~
это побитовое логическое НЕ, оно заменяет каждый бит 0 в числе на 1, а каждую 1 на 0.Я запишу число в битовом виде, а чтобы было понятнее вначале буду писать префикс 0b.
Хоть наши переменные 32 бита, в примерах я буду использовать лишь 8 бит.
~ 0b10001001 = 0b01110110
&
это побитовое логическое И (логическое умножение, Конъюнкция) Конъюнкция — ВикипедияПроще говоря, если оба числа 1, то результат 1, иначе 0.
И это надо сделать с отдельными битами двух чисел (поразрядно).
0b10001001 & 0b11110000 = 0b10000000
Вернёмся к нашим флагам. У нас по факту 32 независимых друг от друга переменных.
Но вот проблема, они все внутри одной переменной и вы не можете поменять так просто конкретную.
FCVAR_NOTIFY представляет собой число, где 1 стоит только в одном бите, который отвечает за указанный флаг.
К примеру:
FCVAR_NOTIFY = 0b00001000
Теперь сделаем наши преобразования
Cvar_FF.Flags = Cvar_FF.Flags & ~FCVAR_NOTIFY;
Мы вычисляем новое значение флагов в зависимости от старого.
Представим что данный код выполняется и подставим в правой части числа в значениях переменных которые бы они имели в момент выполнения.
Cvar_FF.Flags = 0b10001001 & ~0b00001000;
Инверсия битов имеет больший приоритет и выполнится первой.
Cvar_FF.Flags = 0b10001001 & 0b11110111;
Теперь выполним логическое И, и можно заметить, что второе значение, это все 1, кроме того бита, где находится нужный нам бит флага.
А то как работает логическое умножение даёт нам понять:
X & 1 = X
, X & 0 = 0
Т.е. где 1 останется тот же флаг что и был, а где 0 флаг станет 0.
Cvar_FF.Flags = 0b10000001;
А вот бывшее значение
0b10001001
Как можно увидеть, измениться лишь значение 4го бита, он станет равен 0.
Т.е. Эта запись логически обозначает: "Поставить флагу FCVAR_NOTIFY значение 0"
Для проверки наличия флага используется логичкское И с маской флага и сравнивается с 0
if (Cvar_FF.Flags & FCVAR_NOTIFY != 0)
А для установки флага (присвоения 1) - логическое ИЛИ с маской флага
Cvar_FF.Flags = Cvar_FF.Flags | FCVAR_NOTIFY;
Последнее редактирование: