Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.
Показано с 1 по 7 из 7
  1. #1
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

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

    Почему не следует возвращать строки/массивы напрямую

    Всем привет.

    Раньше, когда люди спрашивали, чем плохо то, что функция возвращает строку, основным (и самым очевидным) доводом было лишнее копирование данных: т.е. сначала строка/массив копируется в стек при возврате значения из функции, затем ещё раз копируется из стека в другой массив, к которому идёт присваивание значения. Но, внезапно, для некоторых ленивых скриптеров это не аргумент...
    Не так давно получилось найти новый изъян, который (может быть) переубедит ещё несколько скриптеров: баг с возвратом массивов, который может привести к неправильной работе сервера.

    Допустим, у нас есть следующие две функции:
    1. new global_string[12] = {"Hello world"};
    2.  
    3. StringOrigin() {
    4. return global_string;
    5. }
    6.  
    7. ReturnString() {
    8. return StringOrigin();
    9. }
    Довольно просто, так ведь? Есть глобальная строка, и есть функция, которая возвращает эту строку. И ещё есть другая функция, которая возвращает результат первой функции.


    1. new local[12];
    2. strcat(local, StringOrigin(), sizeof(local));
    3. print(local);
    Такой код работает вполне нормально. Нативная функция strcat() вызывает Pawn-функцию StringOrigin(), которая просто возвращает глобальную строку. Это первый "уровень" вложенности возврата массивов (strcat() -> StringOrigin()).


    1. new local[12];
    2. strcat(local, ReturnString(), sizeof(local));
    3. print(local);
    А вот такой код уже не работает. strcat() вызывает функцию ReturnString(), которая возвращает то, что ей возвращает StringOrigin(). Это второй "уровень" вложенности (strcat() -> ReturnString() -> StringOrigin()), и на нём уже проявляется ошибка.
    При подключенном плагине CrashDetect выводится следующее сообщение:
    Код:
    [debug] Run time error 5: "Invalid memory access"

    Решение проблемы очень простое: всегда возвращайте строки через массив, переданный по ссылке.
    1. new string[12] = {"Hello world"};
    2.  
    3. StringOrigin(output[], size = sizeof output) {
    4. strcat(output, string, size);
    5. return 0; // Будем считать, что 0 - код успешного выполнения этой функции.
    6. }
    7.  
    8. ReturnString(output[], size = sizeof output) {
    9. return StringOrigin(output, size); // Нет ничего плохого в дальнейшем возврате возвращаемого значения
    10. // из StringOrigin(), т.к. это всего лишь одна ячейка (0), а не массив.
    11. }



    Оригинал примера с возвратом строк: https://github.com/sampctl/pawn-array-return-bug
    Перевод и дополнение: Daniel_Cortez
    Специально для Pro-Pawn.ru
    Копирование данной статьи на других ресурсах без разрешения автора запрещено!
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

  2. 6 пользователя(ей) сказали cпасибо:
    DeimoS (17.08.2019) execution (18.08.2019) Gressie (20.08.2019) Osetin (18.08.2019) Outsider (18.08.2019) Web (22.08.2019)
  3. #2
    Аватар для Alpano
    Пользователь

    Статус
    Оффлайн
    Регистрация
    06.02.2017
    Сообщений
    123
    Репутация:
    16 ±
    Простите, недогоняю вообще.
    Появилисъ проблемы с функцией:
    PHP код:
    stock FixSQLiteRusText(const text[], len,needlow 1){
        new 
    textEx[256];
        
    strcat(textEx,text,len+1);
        
    strdel(textEx,strlen(textEx),len+1);
        for(new 
    0<= strlen(textEx); f++){
            if(!
    textEx[f]) textEx[f] = text[f];
            if(-
    65 textEx[f] < 0)
                
    textEx[f]+=256;
            if(
    needlow && textEx[f] == '_'textEx[f] = ' ';
        }
        return 
    textEx;

    Приходится юзатъ с массивами которые содержат кириллицу из БД. (SQLite):
    PHP код:
    new buffer[70];
    db_get_field_assoc(u_result"bNick"buffer,70);
    strcat(string,FixSQLiteRusText(buffer,strlen(buffer))); 
    прошу прощения если быдлокод, но увы.
    помогите пожалуйста :)

    сам трабл:
    PHP код:
    [17:50:08] [debugRun time error 3"Stack/heap collision (insufficient stack size)"
    [17:50:08] [debug]  Stack pointer (STKis 0x3256D4heap pointer (HEAis 0x325810
    [17:50:08] [debugAMX backtrace:
    [
    17:50:08] [debug#0 0024595c in FixSQLiteRusText (text[]=@00325f10 "OnlyCjeat", len=9, needlow=1) at drift.pwn:5422 
    Последний раз редактировалось Alpano; 22.08.2019 в 20:40.
    MyProject:
    DriftEmpire©

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

  4. #3
    Аватар для DeimoS
    Модератор?

    Статус
    Оффлайн
    Регистрация
    27.01.2014
    Адрес
    Восточный Мордор
    Сообщений
    5,588
    Репутация:
    1984 ±
    Так как название темы довольно говорящее и, скорее всего, на него будут натыкаться и те, кто не знаком с основными проблемами возврата строк, стоит отметить так же и то, что при лишнем копировании данных увеличивается количество потребляемого стека на каждый неоднократный вызов функции.
    (пояснение для тех, кто лишнее копирование данных может рассматривать именно со стороны лишнего действия, а не лишнего потребления памяти)

    То бишь, в этом примере:
    1. native printf(const format[], {Float,_}:...);
    2. new global_string[12] = {"Hello world"};
    3.  
    4. main()
    5. {
    6. printf("%s", StringOrigin());
    7. }
    8.  
    9.  
    10. StringOrigin() {
    11. return global_string;
    12. }

    Размер занятого стека будет равен где-то 20 ячейкам.

    А вот в этом примере:
    1. native printf(const format[], {Float,_}:...);
    2. new global_string[12] = {"Hello world"};
    3.  
    4. main()
    5. {
    6. printf("%s %s", StringOrigin(), StringOrigin());
    7. }
    8.  
    9.  
    10. StringOrigin() {
    11. return global_string;
    12. }

    Размер занятого стека возрастёт где-то на 13 ячеек (12 на хранение содержимого массива + 1 ячейка на хранение адреса функции StringOrigin) и станет равным 33-ём. И если мы добавим ещё один такой вызов внутрь printf, размер стека увеличится ещё на 13 ячеек. И так далее.

    При этом, если вызывать глобальный массив напрямую, то, сколько бы раз подряд вы его не вызывали, его содержимое не будет копироваться в стек и никакого подобного "раздутия" происходить не будет.
    Если уж очень хочется, чтоб код вызова массива выглядел как код вызова функции, то либо используйте вариант, показанный Daniel_Cortez (передавайте массив по ссылке), либо используйте макросы:
    1. new global_string[12] = {"Hello world"};
    2. #define StringOrigin() global_string
    3. // И в последующем коде можно спокойно использовать "StringOrigin()", не боясь за "раздутие" стека
    Связаться со мной в VK можно через личные сообщения этой группы
    Заказы не принимаю

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

    Steve Pavlina

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

    Статус
    Оффлайн
    Регистрация
    09.08.2019
    Сообщений
    45
    Репутация:
    9 ±
    Вообще странно, что язык существует уже столько лет, а люди так и не придумали обход для этой проблемы. Можно было бы написать какую-нибудь библиотеку, которая будет работать через байт-код. Строка будет указателем на первую ячейку строки в памяти. Выделять строку можно через инструкции (либо использовать y_malloc). Удалять же можно автоматический, например, через "деконструкторы" (благо такие, насколько мне известно, имеются в pawn уже давно), либо через функцию. Такие строки можно и сравнивать и быстро переприсваивать.

  6. #5
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    Цитата Сообщение от DeimoS Посмотреть сообщение
    либо используйте макросы:
    1. new global_string[12] = {"Hello world"};
    2. #define StringOrigin() global_string
    3. // И в последующем коде можно спокойно использовать "StringOrigin()", не боясь за "раздутие" стека
    Маскируясь под вызов функции, такой код всё ещё будет создавать "рекламу" обычному возврату через стек. ИМХО, такая себе полумера =/

    Цитата Сообщение от vvw Посмотреть сообщение
    Вообще странно, что язык существует уже столько лет, а люди так и не придумали обход для этой проблемы. Можно было бы написать какую-нибудь библиотеку, которая будет работать через байт-код. Строка будет указателем на первую ячейку строки в памяти. Выделять строку можно через инструкции (либо использовать y_malloc). Удалять же можно автоматический, например, через "деконструкторы" (благо такие, насколько мне известно, имеются в pawn уже давно), либо через функцию. Такие строки можно и сравнивать и быстро переприсваивать.
    Есть кое-что похожее, в виде плагина: https://github.com/IllidanS4/PawnPlus
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

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

    Статус
    Оффлайн
    Регистрация
    27.01.2014
    Адрес
    Восточный Мордор
    Сообщений
    5,588
    Репутация:
    1984 ±
    Цитата Сообщение от Daniel_Cortez Посмотреть сообщение
    Маскируясь под вызов функции, такой код всё ещё будет создавать "рекламу" обычному возврату через стек. ИМХО, такая себе полумера =/
    Так чтоб подобного не было, нужно везде, где подобный способ указывается, уточнять, что возврат через стек, при неправильном использовании - зло. Те, кому нет дела до проблем с возвратом стека, будут его использовать даже если использование макросов возвести в ранг табу. Для остальных же лучше объяснить как пользоваться инструментом правильно, а не замалчивать его существование только потому, что при неправильном использовании это может создать проблемы. Собственно, в своём дополнении я и пытался акцентировать внимание на правильном использовании.
    Ну это моё ИМХО, естественно.
    Связаться со мной в VK можно через личные сообщения этой группы
    Заказы не принимаю

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

    Steve Pavlina

  8. #7
    Аватар для Daniel_Cortez
    "Это не хак, это фича"

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    2,192
    Репутация:
    2589 ±
    На днях пофиксил этот баг; исправление предложено к включению принято в компилятор и уже доступно в последнем неофициальном релизе.

    Думаю, после выхода компилятора версии 3.10.11 точно можно будет закрыть эту тему за неактуальностью.
    Индивидуально в ЛС по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).

 

 

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

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

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

Ваши права

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