Конференция "KOL" » KOL 3.23 [Delphi, Windows]
 
  • Thaddy © (26.05.15 12:20) [60]

       Form.Font.ReleaseHandle;
       Form.Font.AssignHandle(GetStockObject(DEFAULT_GUI_FONT));



    But I also have a better, but more complicated solution.
  • Netspirit (10.03.17 12:59) [61]
    Ошибка в PAS_ONLY версии функции StrScan - функция никогда не возвращает nil, что приводит к ошибке, если символ не найден. Правильный код:


    function StrScan(Str: PAnsiChar; Chr: AnsiChar): PAnsiChar;
    begin
     Result := nil;
     if Str = nil then Exit;
     
     while Str^ <> #0 do
     begin
       if Str^ = Chr then
       begin
         Result := Str;
         Break;
       end;
       Inc(Str);
     end;
    end;

  • DKOL (16.03.17 13:06) [62]
    А если StrScan исправить, то другие функции начнут глючить... Во многих местах не проверки на nil

    ps. вроде как то обсуждалось уже, надо поискать..
  • Netspirit (16.03.17 15:11) [63]

    > А если StrScan исправить, то другие функции начнут глючить.

    Нет, в том то и смысл, что из описания этой функции следует, что она должна вернуть nil, если символ не найден. Соответственно, весь код и так проверяет возвращаемый результат. И в случае PAS_ONLY успешно падает при ненахождении символа. Когда-то в старых версиях этот же баг был и в ASM-версии, но его исправили, а в PAS - забыли.


    > вроде как то обсуждалось уже, надо поискать

    Сравнительно недавно то же обсуждали и правили в WStrScan. Так сказать, по образу и подобию...
  • Netspirit (16.03.17 15:32) [64]
    Напомню. Оригинал выглядит так:
    function StrScan(Str: PAnsiChar; Chr: AnsiChar): PAnsiChar;
    begin
       while Str^ <> Chr do
       begin
           if Str^ = #0 then break;
           inc(Str);
       end;
       Result := Str;
    end;


    В случае ненахождения символа возвращается не nil, а указатель на терминирующий #0.

    Ну, и без проверки if Str = nil then Exit сама функция StrScan не падает только потому, что при вызове её обычно как if StrScan(PChar(S), 'A') <> nil then, Delphi при приведении строки к PChar вставляет ещё один вызов функции, которая проверяет строку на пустоту и для пустой строки возвращает действительный указатель на заранее заготовленный символ #0, лежащий где-то в области констант или глобальных переменных.
    Так вот, если захочется ускориться, чтобы Delphi не вызывал дополнительную функцию, и вызвать StrScan как if StrScan(Pointer(S), 'A') <> nil then, то без проверки параметра Str на nil функция упадёт.

    Я только что глянул, WStrScan тоже надо поправлять.
  • DKOL (17.03.17 08:06) [65]

    > Нет, в том то и смысл, что из описания этой функции следует,
    >  что она должна вернуть nil, если символ не найден.

    Например функция StrScan, проверок нету.


    > function StrScan(Str: PAnsiChar; Chr: AnsiChar): PAnsiChar;
    .....

    Изменения на svn внёс, StrScan пофиксил заодно.

    WStrScan и ASM версии - не смотрел...
  • DKOL (17.03.17 08:07) [66]

    > Например функция StrScan, проверок нету.


    Имелась ввиду функция StrCat, копи-паста она такая)
  • Netspirit (17.03.17 12:57) [67]
    А-а-а, там ещё и StrCat есть. А может кто-то на пальцах объяснить логику этой функции? Как я понимаю, эта функция объединяет две строки в одну, результат заносит по указателю в Dest, в качестве результата возвращает указатель на Dest. Типа, так?

    procedure Test;
    var
     Src, Dst: string;
    begin
     Dst := 'ABC';
     Src := 'DEF';
     
     MessageBox(0, StrCat(PAnsiChar(Dst), PAnsiChar(Src)), '', 0);
     // PS: Этот код выдаёт ошибку!
    end;


    Вопрос: а кто ж в этой фунции занимается выделением памяти под результат объединения, а затем должен освободить эту память?
    Чтоб далеко не ходили, приведу оригиналы этих функций:

    function StrScan(Str: PAnsiChar; Chr: AnsiChar): PAnsiChar;
    begin
       while Str^ <> Chr do
       begin
           if Str^ = #0 then break;
           inc(Str);
       end;
       Result := Str;
    end;

    function StrCopy( Dest, Source: PAnsiChar ): PAnsiChar;
    var L: Integer;
    begin
       L := StrLen(Source);
       Move(Source^, Dest^, L+1);
       Result := Dest;
    end;

    function StrCat( Dest, Source: PAnsiChar ): PAnsiChar;
    begin
     // Итак. Оригинальная StrScan найдёт #0 в конце Dest
     // и вернёт нам указатель на конец Dest
     // Функция StrCopy должна скопировать Source в конец Dest
     // Спрашивается: а кто должен дать гарантию, что после
     // оригинального Dest есть свободное место, откуда оно взялось,
     // и кто отвечает за его освобождение?
     StrCopy( StrScan( Dest, #0 ), Source );
     Result := Dest;
    end;


    Исходя из выше озвученных рассуждений правильное использование должно выглядеть так:

    procedure Test;
    var
     Src, Dst: string;
    begin
     Dst := 'ABC';
     Src := 'DEF';
     SetLength(Dst, Length(Dst)+Length(Src)); // Выделили память в Dst, чтобы уместить Dst+Src
     
     MessageBox(0, StrCat(PAnsiChar(Dst), PAnsiChar(Src)), '', 0);
    end;


    Где в цепочке вызовов StrCat->StrCopy+StrLen->StrScan находится термин "fast" из описания функции StrCat (в отличие от обычного Dst := Dst + Src;)?

    Нюанс: в моей поправке StrScan символ #0 не найдётся. Потому что этот символ - это ограничитель null-terminated строки (используемой с типом PChar). Он не может являться частью строки (в отличие от дельфийского string). Единственная необходимость в получении указателя на #0 - это определение конца строки. И зачем это необходимо? Для определения количества символов, расположенных по указателю PChar (до терминирующего #0), используется функция StrLen().
  • DKOL (23.03.17 12:01) [68]

    > Нюанс: в моей поправке StrScan символ #0 не найдётся.

    Ну вот, как и говорил, исправили одно - сломали другое...


    > Единственная необходимость в получении указателя на #0 -
    >  это определение конца строки. И зачем это необходимо? Для
    > определения количества символов, расположенных по указателю
    > PChar (до терминирующего #0), используется функция StrLen().
    >

    В StrCat же и используется.


    > Где в цепочке вызовов StrCat->StrCopy+StrLen->StrScan находится
    > термин "fast" из описания функции StrCat (в отличие от обычного
    > Dst := Dst + Src;)?

    Не совсем понимаю, а зачем здесь термин fast?


    > // Спрашивается: а кто должен дать гарантию, что после
    >   // оригинального Dest есть свободное место, откуда оно
    > взялось,
    >   // и кто отвечает за его освобождение?


    Гарантий никто не дает. Просто бывает с PChar строками так работают. Выделяют память с запасом, а потом через StrCat клеят.
  • Netspirit (23.03.17 16:20) [69]

    > исправили одно - сломали другое...

    Описание StrScan():
    {* Fast search of given character in a string. Pointer to found character  (or nil) is returned. }

    То-есть, должна вернуть nil, если искомый символ не найден. А условием ненахождения искомого символа является достижение символа #0. Тогда для поиска #0 нужно добавлять ещё условие. Например, так:
    function StrScan(Str: PAnsiChar; Chr: AnsiChar): PAnsiChar;
    begin
     Result := nil;
     if Str = nil then Exit;
     
     while Str^ <> #0 do
     begin
       if Str^ = Chr then
       begin
         Result := Str;
         Exit;
       end;
       Inc(Str);
     end;
     
     if (Chr = #0) and (Str^ = #0) then Result := Str;  
    end;



    > а зачем здесь термин fast?

    Я ни при чем. Описание функции StrCat:
    {* Append source string to destination (fast). Pointer to Dest is returned. }


    > Выделяют память с запасом, а потом через StrCat клеят.

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

    Сделайте эту StrCat так:

    function StrCat(Dest, Source: PAnsiChar): PAnsiChar;
    begin
     Result := Dest;
     if (Dest = nil) or (Source = nil) then Exit;
     Move(Source^, Dest[StrLen(Dest)], StrLen(Source)+1);
    end;
  • Netspirit (23.03.17 17:02) [70]
    PS: условие and (Str^ = #0) в вышеприведенному коде StrScan, наверное, можно выбросить.
  • Dimaxx © (23.03.17 21:07) [71]
    >> Сделайте эту StrCat так:
    Вкорне неверно. У вас не выделена память для строки Dest, чтобы принять Source простым копированием данных. Для начала нужно выделить память, а уж после копировать.
  • Dimaxx © (23.03.17 21:16) [72]
    Я чет ваще не тащу - зачем так сделано?

    function StrCopy( Dest, Source: PAnsiChar ): PAnsiChar;
    var L: Integer;
    begin
       L := StrLen(Source);
       Move(Source^, Dest^, L+1);
       Result := Dest;
    end;



    Это ж бред. К примеру, есть у нас две строки "AAA" и "BBB". Мы передаем указатели в StrCopy и что получается? "BBB" копируется после "AAA" и получится "AAABBB". Но! Память для строки "AAA" у нас выделена только на три(!!) символа! А мы тупо копируем в область после первой строки (которая по идее ей еще не принадлежит) вторую строку. Это все равно что пытаться копировать один массив, выделенный GetMem, в конец второго массива. Но память-то выделена только под каждый размер массивов раздельно! Для слияния массивов нам надо Realloc памяти первого массива, с размером суммарных размеров массивов.
  • Dimaxx © (23.03.17 21:21) [73]
    И еще. Move(Source^, Dest^, L+1) скопирует содержимое Source в НАЧАЛО Dest!!! Разве это верно?
  • Dimaxx © (23.03.17 21:24) [74]
    Тьфу, я все напутал - думаю про StrCat, а пишу про StrCopy...

    StrCopy должна сначала сделать длину Dest равной длине Source, а потом копировать. Ведь размеры строк могут различаться.

    А вот в [72] все сказанное верно для StrCat в [69].
  • Netspirit (24.03.17 11:40) [75]

    > StrCopy должна сначала сделать длину Dest равной длине Source

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


    > скопирует содержимое Source в НАЧАЛО Dest

    Это так и задумано - вызывающий сам передаёт указатель, по которому нужно записать данные. А указывать этот указатель может куда угодно. Например, StrCat находит указатель на завершающий #0 и передаёт его в эту функцию.

    Напоминаю: если вы пользуетесь типом string, то вместо StrCat лучше использовать Dst := Dst + Src, а если таких операций много (типа, for i := 1 to 100000 do S := S + S2;) то такой код заменяется на такой:
    L := Length(S2);
    if L = 0 then Exit;
    SetLength(S, 100000 * L);
    i := 1;
    while i <= 100000 do
    begin
     Move(S2[1], S[i], L);
     Inc(i, L);
    end;

  • Dimaxx © (24.03.17 23:53) [76]
    >> для этих функций вызывающий сам должен беспокоиться куда будут помещаться новые данные
    Не соглашусь. Я, к примеру, этими функциями не пользовался и если бы стал - даже не подумал бы, что я сам должен чего-то увеличивать, прежде чем вызывать. Это все должно делаться внутри функции, а не до ее вызова.
  • Netspirit (27.03.17 11:16) [77]
    Так и я тоже не подозревал о существовании этих функций. Просто пришлось делать выводы, глядя на их код.
  • Dimaxx © (09.04.17 10:54) [78]
    Еще один момент. Интенсивное использование Stream (проверял на открытом большом файле и из него копировал подряд куски в другие файлы - порядка 1000+ файлов) роняет приложение. Сначала все идет норм, потом АВ. То же самое на стандартном дельфевом TFileStream без проблем и падений.
  • DKOL (10.04.17 08:33) [79]
    Dimaxx, а как то отладчиком реально поймать? или тулзами типа madExcept?
 
Конференция "KOL" » KOL 3.23 [Delphi, Windows]
Есть новые Нет новых   [118580   +25][b:0.001][p:0.002]