AnsiReverseString — Функция Delphi


Содержание

Работа со строковыми типами данных в Delphi

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

Строка — это последовательность символов. В Object Pascal существует несколько строковых типов. Вот основные из них:

Для большинства целей подходит тип AnsiString (иногда называется Long String ).

Стандартные функции обработки строк:

1) Функция Length(Str: String) — возвращает длину строки (количество символов). Пример:

var
Str: String; L: Integer;
< . >
Str := ‘Hello!’ ;
L := Length(Str);

2) Функция SetLength(Str: String; NewLength: Integer) позволяет изменить длину строки. Если строка содержала большее количество символов, чем задано в функции, то «лишние» символы обрезаются. Пример:

var Str: String;
< . >
Str := ‘Hello, world!’ ;
SetLength(Str, 5);

3) Функция Pos(SubStr, Str: String) — возвращает позицию подстроки в строке. Нумерация символов начинается с единицы (1). В случае отсутствия подстроки в строке возращается 0. Пример:

var Str1, Str2: String; P: Integer;
< . >
Str1 := ‘Hi! How do you do?’ ;
Str2 := ‘do’ ;
P := Pos(Str2, Str1);

4) Функция Copy(Str: String; Start, Length: Integer) — возвращает часть строки Str, начиная с символа Start длиной Length. Ограничений на Length нет — если оно превышает количество символов от Start до конца строки, то строка будет скопирована до конца. Пример:

var Str1, Str2: String;
< . >
Str1 := ‘This is a test for Copy() function.’ ;
Str2 := Copy(Str1, 11, 4);

5) Процедура Delete(Str: String; Start, Length: Integer) — удаляет из строки Str символы, начиная с позиции Start длиной Length. Пример:

var Str1: String;
< . >
Str1 := ‘Hello, world!’ ;
Delete(Str1, 6, 7);

6) Функции UpperCase(Str: String) и LowerCase(Str: String) преобразуют строку соответственно в верхний и нижний регистры:

var Str1, Str2, Str3: String;
< . >
Str1 := ‘hELLo’ ;
Str2 := UpperCase(Str1); < Str2 = "HELLO" >
Str3 := LowerCase(Str1);

Строки можно сравнивать друг с другом стандартным способом:

var Str1, Str2, Str3: String; B1, B2: Boolean;
< . >
Str1 := ‘123’ ;
Str2 := ‘456’ ;
Str3 := ‘123’ ;
B1 := (Str1 = Str2); < B1 = False >
B2 := (Str1 = Str3);

Если строки полностью идентичны, логическое выражение станет равным True.

Дополнительные функции обработки строк:

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

1) PosEx(SubStr, Str: String; Offset: Integer) — функция аналогична функции Pos() , но позволяет задать отступ от начала строки для поиска. Если значение Offset задано (оно не является обязательным), то поиск начинается с символа Offset в строке. Если Offset больше длины строки Str, то функция возратит 0. Также 0 возвращается, если подстрока не найдена в строке. Пример:

uses StrUtils;
< . >
var Str1, Str2: String; P1, P2: Integer;
< . >
Str1 := ‘Hello! How do you do?’ ;
Str2 := ‘do’ ;
P1 := PosEx(Str2, Str1, 1); < P1 = 12 >
P2 := PosEx(Str2, Str1, 15);

2) Функция AnsiReplaceStr(Str, FromText, ToText: String) — производит замену выражения FromText на выражение ToText в строке Str. Поиск осуществляется с учётом регистра символов. Следует учитывать, что функция НЕ изменяет самой строки Str, а только возвращает строку с произведёнными заменами. Пример:

uses StrUtils;
< . >
var Str1, Str2, Str3, Str4: String;
< . >
Str1 := ‘ABCabcAaBbCc’ ;
Str2 := ‘abc’ ;
Str3 := ‘123’ ;
Str4 := AnsiReplaceStr(Str1, Str2, Str3);

3) Функция AnsiReplaceText(Str, FromText, ToText: String) — выполняет то же самое действие, что и AnsiReplaceStr(), но с одним исключением — замена производится без учёта регистра. Пример:

uses StrUtils;
< . >
var Str1, Str2, Str3, Str4: String;
< . >
Str1 := ‘ABCabcAaBbCc’ ;
Str2 := ‘abc’ ;
Str3 := ‘123’ ;
Str4 := AnsiReplaceText(Str1, Str2, Str3);

4) Функция DupeString(Str: String; Count: Integer) — возвращает строку, образовавшуюся из строки Str её копированием Count раз. Пример:

uses StrUtils;
< . >
var Str1, Str2: String;
< . >
Str1 := ‘123’ ;
Str2 := DupeString(Str1, 5);

5) Функции ReverseString(Str: String) и AnsiReverseString(Str: AnsiString) — инвертируют строку, т.е. располагают её символы в обратном порядке. Пример:

uses StrUtils;
< . >
var Str1: String;
< . >
Str1 := ‘0123456789’ ;
Str1 := ReverseString(Str1);

6) Функция IfThen(Value: Boolean; ATrue, AFalse: String) — возвращает строку ATrue, если Value = True и строку AFalse если Value = False. Параметр AFalse является необязательным — в случае его отсутствия возвращается пустая строка.

uses StrUtils;
< . >
var Str1, Str2: String;
< . >
Str1 := IfThen(True, ‘Yes’ ); < Str1 = "Yes" >
Str2 := IfThen(False, ‘Yes’ , ‘No’ );

Мы рассмотрели функции, позволяющие выполнять со строками практически любые манипуляции. Как правило, вместо строки с указанным типом данных, можно использовать и другой тип — всё воспринимается одинаково. Но иногда требуются преобразования. Например, многие методы компонент требуют параметр типа PChar , получить который можно из обычного типа String функцией PChar(Str: String) :

uses ShellAPI;
< . >
var FileName: String;
< . >
FileName := ‘C:\WINDOWS\notepad.exe’ ;
ShellExecute(0, ‘open’ , PChar(FileName), » , » , SW_SHOWNORMAL);

Тип Char представляет собой один-единственный символ. Работать с ним можно как и со строковым типом. Для работы с символами также существует несколько функций:

Chr(Code: Byte) — возвращает символ с указанным кодом (по стандарту ASCII):

Ord(X: Ordinal) — возвращает код указанного символа, т.е. выполняет противоположное действие функции Chr() :

var X: Integer;
< . >
X := Ord( ‘F’ );

Из строки можно получить любой её символ — следует рассматривать строку как массив. Например:

var Str, S: String; P: Char;
< . >
Str := ‘Hello!’ ;
S := Str[2]; < S = "e" >
P := Str[5];

В этой статье описаны основные приёмы работы со строковыми типами данных. Как правило, этих данных достаточно для написания любого алгоритма.

Ссылки по теме

Популярные статьи
Информационная безопасность Microsoft Офисное ПО Антивирусное ПО и защита от спама Eset Software


Бестселлеры
Курсы обучения «Atlassian JIRA — система управления проектами и задачами на предприятии»
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год. Электронный ключ
Microsoft Windows 10 Профессиональная 32-bit/64-bit. Все языки. Электронный ключ
Microsoft Office для Дома и Учебы 2020. Все языки. Электронный ключ
Курс «Oracle. Программирование на SQL и PL/SQL»
Курс «Основы TOGAF® 9»
Microsoft Windows Professional 10 Sngl OLP 1 License No Level Legalization GetGenuine wCOA (FQC-09481)
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год. Электронный ключ
Windows Server 2020 Standard
Курс «Нотация BPMN 2.0. Ее использование для моделирования бизнес-процессов и их регламентации»
Антивирус ESET NOD32 Antivirus Business Edition
Corel CorelDRAW Home & Student Suite X8

О нас
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.

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

Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе.

AnsiReverseString — Функция Delphi

Голова опухла настолько, что уже не соображаю, казалось бы, элементарное: как в DelphiXE тупо сконверировать из AnsiString в обычный String, который в DelphiXE в Unicode? B какой Unicode принят в DelphiXE по умолчанию: utf-8, utf-16 или какой-либо другой?

а приравнять не пробовал?

А не прокатывает, когда Edit.Text передаешь как AnsiString-параметр в функцию, равно как и присваиваешь полю результат функции в AnsiString. Казалось бы, проще функцию переписать, но таких несколько тысяч, а сроки поджимают.

Если я понял правильно, то вместо AnsiString я должен использовать либо CirillicString либо AnsiString(1251) и все будет хорошо, или я не прав?
Eraser, премного благодарен за ссылки, многое встало на свои места.

> AlekVolsk (13.04.2011 00:08:04) [4]

Если локализация совпадает, то кодировку можно не указывать, но лучше это делать всегда.

var
as: AnsiString;
us: string;
begin
as := «Bla-bla-bla»;
us := string(as);
us := «foo»;
as := AnsiString(us);
end;

type
CirillicString = type AnsiString(1251);
var
s: CirillicString;
begin
s := edit1.Text;
end;

Компилятор пишет: Implicit string cast with potential data loss from «TCaption» to «CirillicString», т.е. неявный строковый бросок с потенциальной потерей данных от «TCaption» до «CirillicString». В каких случаях возможна потеря данных из CirillicString и как этого избежать?

Если откровенно туплю — извиняйте, 40 часов уже изза компа не выхожу

> В каких случаях возможна потеря данных из CirillicString
> и как этого избежать?

При преобразовании из Unicode в локальную кодировку, когда в целевой кодировке нет символов исходной.

соответственно, если в edit1.text только символы алфавита (лат/рус), знаки препинания и цифры, при полном отсутствии каких-либо управляющих символов типа табуляции, то все будет ок. или я не прав?
Но в любом случае, хотелось бы как-то избавиться от предупреждения компилятора, это возможно в данном частном случае? да — как, нет — почему?

> при полном отсутствии каких-либо управляющих символов типа
> табуляции

управляющие символы имеют код


> это возможно в данном частном случае? да — как, нет — почему?

Да.
Как — я уже написал.

Все получилось.
Всем премного благодарен!
Приношу всем свои извинения за возможно потраченное на меня время и нервы.

Блин, во всей группе проектов на 2 exe и 16 dll общей численностью более 40 тысяч строк при переходе с D2006 на DXE править пришлось менее 30 строк кода! А пока догнал, что к чему, 2 недели времени и нервов угробил!
Еще раз всем откликнувшимся огромное спасибо!

Тонкости работы со строками в Delphi

Written on 07 Января 2009 . Posted in Delphi

ОГЛАВЛЕНИЕ

Виды строк в Delphi

Для работы с кодировкой ANSI в Delphi существует три вида строк — AnsiString, ShortString и PChar. Между собой они различаются тем, где хранится строка и как выделяется и освобождается память для неё. Зарезервированное слово string по умолчанию означает тип AnsiString, но если после неё стоит число в квадратных скобках, то это означает тип ShortString, а число — ограничение по длине. Кроме того, существует опция компилятора Huge strings (управляется также директивами компилятора <$H+/->и <$LONGSTRINGS ON/OFF>), которая по умолчанию включена, но если её выключить, то слово string станет эквивалентно ShortString или, что то же самое, string[255]. Эта опция введена для обратной совместимости с Turbo Pascal, в новых программах отключать её нет нужды.

Наиболее просто устроен тип ShortString. Это — массив символов с индексами от 0 до N, где N — число символов, указанное при объявлении переменной (в случае использования идентификатора ShortString N явно не указывается и равно 255). Нулевой элемент массива хранит текущую длину строки, которая может быть меньше или равна объявленной (эту длину мы будем далее обозначать M), элементы с индексами от 1 до M — символы, составляющие строку. Значения элементов с индексами M+1..N не определены. Все стандартные функции для работы со строками игнорируют эти символы. В памяти такая переменная всегда занимает N+1 байт.

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

В системе приняты так называемые нуль-терминированные строки: строка передаётся указателем на её первый символ, длина строки отдельно нигде не хранится, признаком конца строки считается встретившийся в цепочке символов #0. Длина таких строк ограничена только доступной памятью и способом адресации (т.е. в Windows это 4294967295 символов). Для работы с такими строками предусмотрен тип PChar. Переменная такого типа является указателем на начало строки. В литературе нередко можно встретить утверждение, что PChar=^Char, однако это неверно: тип PChar встроен в компилятор и не выводится из других типов. Это позволяет выполнять с ним операции, недопустимые для других указателей. Во-первых, если P — переменная типа PChar, то допустимо обращение к отдельным символам строки с помощью конструкции P[N], где N — целочисленное выражение, определяющее номер символа (в отличие от типа ShortString, здесь символы нумеруются с 0, а не с 1). Во-вторых, к указателям типа PChar разрешено добавлять и вычитать целые числа, смещая указатель на соответствующее количество байт вверх или вниз (здесь речь идёт только об использовании операторов «+» и «-«; адресная арифметика с помощью процедур Inc и Dec доступна для любых типизированных указателей, а не только для PChar).

При использовании PChar программист целиком и полностью отвечает за выделение памяти для строки и за её освобождение. Именно это и служит основным источником ошибок у новичков: они пытаются работать с такими строками так же, как и с AnsiString, надеясь, что операции с памятью будут выполнены автоматически. Это очень грубая ошибка, способная привести к самым непредсказуемым последствиям.

Хотя программист имеет полную свободу выбора в том, как именно выделять и освобождать память для нуль-терминированных строк, в большинстве случаев самыми удобными оказываются специально предназначенные для этого функции StrNew, StrDispose и т.п. Их удобство заключается в том, что менеджер памяти выделяет памяти чуть больше, чем требуется для хранения строки, и в эту дополнительную память записывается, сколько байт было выделено. Благодаря этому функция StrDispose удаляет ровно столько памяти, сколько было выделено, даже если в середину выделенного блока был записан символ #0, уменьшающий длину строки.

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

Тип AnsiString объединяет достоинства типов ShortString и PChar: строки имеют фактически неограниченную длину, заботиться о выделении памяти для них не нужно, в их конец автоматически добавляется символ #0, что делает их совместимыми с системными строками (впрочем, эта совместимость не абсолютная; как и когда можно использовать AnsiString в функциях API, можно прочитать здесь).

Переменная типа AnsiString — это указатель на первый символ строки, как и в случае PChar. Разница в том, что перед этой строкой в память записывается дополнительная информация: длина строки и счётчик ссылок. Это позволяет компилятору генерировать код, автоматически выделяющий, перераспределяющий и освобождающий память, выделяемую для строки. Работа с памятью происходит совершенно прозрачно для программиста, в большинстве случаев со строками AnsiString можно работать, вообще не задумываясь об их внутреннем устройстве. Символы в таких строках нумеруются с единицы, чтобы облегчить перенос старых программ, использовавших ShortString.

Счётчик ссылок позволяет реализовать то, что называется copy-on-demand, копирование по необходимости. Если у нас есть две переменные S1, S1 типа AnsiString, присваивание вида S1 := S2 не приводит к копированию всей строки. Вместо этого в указатель S1 копируется значение указателя S2, а счётчик ссылок строки увеличивается на единицу. В дальнейшем, если одну из этих строк потребуется изменить, она сначала будет скопирована (а счётчик ссылок оригинала, естественно, уменьшен) и только потом изменена, чтобы это изменение не затрагивало остальные переменные.

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

Хранение констант

Для работы с этим примером нам понадобится на форму положить пять кнопок и написать следующие обработчики для них (пример Constants):
В этом примере только нажатие на третью и четвёртую кнопку приводит к появлению надписи Test., Первые два обработчика вызывают исключение Access violation в строках, отмеченных звёздочками, а при нажатии пятой кнопки программа обычно работает без исключений (хотя в некоторых случаях оно всё же может возникнуть), но к слову «Test» добавляется какой-то мусор. Разберёмся, почему так происходит.

Все строковые константы, встречающиеся в программе, компилятор размещает в сегменте кода, в области, управление которой никогда не передаётся. Встретив в первом обработчике константу ‘Test’ и определив, что она относится к типу PChar, компилятор выделяет в этой области пять байт (четыре значащих символа и один завершающий ноль), а в указатель P заносится адрес этой константы. Сегмент кода доступен только для чтения, прав на его изменение система программе в целях безопасности не даёт, поэтому попытка изменить то, что находится в этом сегменте, приводит к закономерному результату — Access violation.

В обработчике второй кнопки происходит почти то же самое, с той лишь разницей, что для константы выделяется на восемь байт больше: т.к. в данном случае константа имеет тип AnsiString, ей нужны ещё 4 байта для хранения длины и 4 — для счётчика ссылок. В переменную S записывается указатель на эту константу. Приводя эту переменную к типу PChar, мы, по сути, просто копируем этот указатель в переменную P, а дальше происходит то же самое — попытка изменить страницу памяти, доступную программе только для чтения с тем же самым результатом.

В третьем случае константа, как и раньше, размещается в сегменте кода. Счётчик ссылок у таких констант всегда равен -1 — это значение указывает менеджеру памяти, что это константа, которая не может быть изменена и память для которой не нужно освобождать. Поэтому при любой попытке изменить переменную, которой присвоена такая константа, срабатывает механизм копирования по необходимости: для строки выделяется место в динамической памяти, затем значение константы копируется в эту область, обновляется значение указателя S, а затем выполняется изменение копии, находящейся в динамической памяти. Так как эта память доступна и для чтения, и для записи, исключение не возникает и всё работает так, как и было задумано.

В четвёртом случае константа также хранится в сегменте кода, но работы с указателем уже нет. Эта константа занимает там пять байт — один байт на длину и четыре — на символы. Переменная S размещается в стеке, занимая там 256 байт, а присваивание ей константы — это копирование значения константы в эту область памяти. Таким образом, в дальнейшем мы работаем не с константой в сегменте кода, а с её копией в стеке, которую можно без проблем модифицировать.

В пятом случае мы получаем указатель на этот участок стека. Обратите внимание, что приведение типов в данном случае не работает: для записи в P адреса первого символа строки приходится использовать оператор получения адреса @. Модификация строки проходит, как и в предыдущем случае, успешно, но при присваивании выражения типа PChar свойству типа AnsiString длина строки определяется по правилам, принятым для PChar, т.е. строка сканируется до обнаружения нулевого символа. Но так как ShortString «не отвечает» за то, что будет содержаться в неиспользуемых символах, там может остаться всякий мусор от предыдущего использования стека. Никакой гарантии, что сразу после последнего символа будет #0, нет. Отсюда и появление непонятных символов на экране.

Общий вывод таков: пока мы не вмешиваемся в работу компилятора с типами ShortString и AnsiString, получаем ожидаемый результат. Работа с этими же строками через PChar в обход стандартных механизмов приводит к появлению проблем. Кроме того, при работе со строками PChar необходимо чётко представлять, где и как выделяется для них память, иначе можно получить неожиданную ошибку.

Сравнение строк

Для типов PChar и AnsiString, которые являются указателями, понятие равенства двух строк может толковаться двояко: либо как равенство указателей, либо как равенство содержимого памяти, на которую эти указатели указывают. Второй вариант предпочтительнее, т.к. он ближе к интуитивному понятию равенства строк. Для типа AnsiString реализован именно этот вариант, т.е. сравнивать такие строки можно, ни о чём не задумываясь. Более сложные ситуации мы проиллюстрируем примером Comparisons. В нём девять кнопок, и обработчик каждой из них иллюстрирует одну из возможных ситуаций.
В данном примере мы увидим надпись «Не равно». Это происходит потому, что в этом случае сравниваются указатели, а не содержимое строк, а указатели здесь будут разные. Попытка сравнить строки PChar с помощью оператора сравнения — весьма распространённая ошибка у начинающих. Для сравнения таких строк следует использовать специальную функцию — StrComp.

Следующий пример, на первый взгляд, в плане сравнения ничем не отличается от только что рассмотренного:
Разница только в том, что строки хранятся не в динамической памяти, а в сегменте кода. Тем не менее, на экране появится надпись «Равно». Это происходит, разумеется, не потому, что сравнивается содержимое строк, а потому, что в данном случае два указателя оказываются равны. Компилятор поступает достаточно интеллектуально: видя, что в разных местах используется одна и та же константа, он выделяет для неё место только один раз, а потом помещает в разные указатели один адрес. Поэтому сравнение даёт правильный (с интуитивной точки зрения) результат.

Такое положение дел только запутывает ситуацию со сравнением PChar: написав подобный тест, человек может сделать вывод, что строки PChar сравниваются не по указателю, а по значению, и действовать под руководством этого заблуждения.

Раз уж мы столкнулись с такой особенностью компилятора, немного отвлечёмся от сравнения строк и копнём этот вопрос немного глубже. В частности, распространяется ли интеллект компилятора на константы типа AnsiString.
В этом примере на экран будет выведено «Равно». Как мы видим, указатели равны, т.е. и здесь компилятор проявил интеллект.

Рассмотрим чуть более сложный случай:
В этом случае указатели окажутся не равны. Действительно, с формальной точки зрения константа типа AnsiString отличается от константы типа PChar: в ней есть счётчик ссылок (равный -1) и длина. Однако если забыть о существовании этой добавки, эти две константы одинаковы: четыре значащих символа и один #0, т.е. компилятор, в принципе, мог бы обойтись одной константой. Тем не менее, на это ему интеллекта уже не хватило.

Но вернёмся к сравнению строк. Как мы знаем, строки AnsiString сравниваются по значению, а PChar — по указателю. А что будет, если сравнить AnsiString с PChar?
Этот код выдаст «Равно». Как мы знаем из предыдущего примера, значения указателей не будут равны, следовательно, производится сравнение по содержанию, т.е. именно то, что и требуется. Если исследовать код, который генерирует компилятор, то можно увидеть, что сначала неявно создаётся строка AnsiString, в которую копируется содержимое строки PChar, а потом сравниваются две строки AnsiString. Сравниваются, естественно, по значению.

Для строк ShortString сравнение указателей невозможно, две таких строки всегда сравниваются по значению. Правила хранения констант и сравнения с другими типами следующие:

1. Константы типа ShortString также размещаются в сегменте кода только один раз, сколько бы раз они ни повторялись в тексте.
2. При сравнении строк ShortString и AnsiString первая сначала конвертируется в тип AnsiString, а потом выполняется сравнение.
3. При сравнении строк ShortString и PChar строка PChar конвертируется в ShortString, затем эти строки сравниваются.

Последнее правило таит в себе подводный камень, который иллюстрируется следующим примером:
Здесь формируется строка типа PChar, состоящая из 299 символов «A». Затем формируется строка ShortString, состоящая из 255 символов «А». Очевидно, что эти строки не равны, потому что имеют разную длину. Тем не менее, на экране появится «Равно».

Происходит это вот почему: строка PChar оказывается больше, чем максимально допустимый размер строки ShortString. Поэтому при конвертировании лишние символы просто отбрасываются. Получается строка длиной 255 символов, которая совпадает со строкой ShortString, с которой мы её сравниваем. Отсюда вывод: если строка ShortString содержит 255 символов, а строка PChar — более 255 символов, и её первые 255 символов совпадают с символами строки ShortString, операция сравнения ошибочно даст положительный результат, хотя эти строки не равны.

Избежать этой ошибки поможет либо явное сравнение длины перед сравнением строк, либо приведение одной из сравниваемых строк к типу AnsiString (второй аргумент при этом также будет приведён к этому типу). Следующий пример даёт правильный результат «Не равно»:
Учтите, что конвертирование в AnsiString — операция дорогостоящая в смысле процессорного времени (в этом примере будут выделены, а потом освобождены два блока памяти), поэтому там, где нужна производительность, лучше вручную сравнить длину, а ещё лучше — вообще по возможности избегать сравнения строк разных типов, так как без конвертирования это в любом случае не обходится.

Теперь зададимся глупым, на первый взгляд, вопросом: если мы приведём строку AnsiString к PChar, будут ли равны указатели? Проверим:
Вполне ожидаемый результат — «Равно». Можно, например, перенести строку из сегмента кода в динамическую память с помощью UniqueString — результат не изменится. Однако выводы делать рано. Рассмотрим следующий пример:
От предыдущего он отличается только тем, что строка S имеет пустое значение. Тем не менее, на экране мы увидим «Не равно». Связано это с тем, что приведение строки AnsiString к типу PChar на самом деле не является приведением типов. Это — скрытый вызов функции _LStrToPChar, и сделано это для того, чтобы правильно обрабатывать пустые строки.

Значение » (пустая строка) для строки AnsiString означает, что память для неё вообще не выделена, а указатель имеет значение nil. Для типа PChar пустая строка — это ненулевой указатель на символ #0. Нулевой указатель также может рассматриваться как пустая строка, но не всегда — иногда это рассматривается как отсутствие какого бы то ни было значения, даже пустого (аналог NULL в базах данных). Чтобы решить это противоречие, функция _LStrToPChar проверяет, пустая ли строка хранится в переменной, и, если не пустая, возвращает этот указатель, а если пустая, то возвращает не nil, а указатель на символ #0, который специально для этого размещён в сегменте кода. Таким образом, в случае пустой строки PChar(S) <> Pointer(S), потому что приведение строки AnsiString к указателю другого типа — это нормальное приведение типов без дополнительной обработки значения.

Побочное изменение

Из-за того, что две одинаковые строки AnsiString разделяют одну область памяти, на неожиданные эффекты можно натолкнуться, если модифицировать содержимое строки в обход стандартных механизмов. Следующий код (пример SideChange) иллюстрирует такую ситуацию:
В этом примере требует комментариев процедура UniqueString. Она обеспечивает то, что счётчик ссылок на строку будет равен единице, т.е. для этой строки делается уникальная копия. Здесь это понадобилось для того, чтобы строка S1 хранилась в динамической памяти, а не в сегменте кода, иначе мы получили бы Access violation, как и во втором случае рассмотренного ранее примера Constants.

В результате работы этого примера на экран будет выедено не «Test», а «Fest», хотя значение S2, казалось бы, не должно меняться, потому что мы изменения, которые мы делаем, касаются только S1. Но более внимательный анализ подсказывает объяснение: после присваивания S2 := S1 счётчик ссылок строки становится равным двум, а сама строка разделяется двумя указателями: S1 и S2. Если бы мы попытались изменить непосредственно S1, сначала была бы создана копия этой строки, а потом были бы сделаны изменения в этой копии, а оригинал, на который указывала бы S2, остался бы без изменений. Но, использовав PChar, мы обошли механизм копирования, поэтому строка осталась в единственном экземпляре, и изменения затронули не только S1, но и S2.

В данном примере всё достаточно очевидно, но в более сложных случаях разработчик программы может и не подозревать, что строка, с которой он работает, разделяется несколькими переменными. Справка Delphi советует сначала обеспечить уникальность копии строки с помощью UniqueString и только потом работать с ней через PChar, если в этом есть необходимость.

Нулевой символ в середине строки

Хотя символ #0 и добавляется в конец каждой строки AnsiString, он уже не является признаком её конца, т.к. длина строки хранится отдельно. Это позволяет размещать символы #0 и в середине строки. Но нужно учитывать, что полноценное преобразование такой строки в PChar невозможно — это иллюстрируется примером Zero (в этом примере на форме одна кнопка и две метки):
В первую метку будет выведено число 9 (длина исходной строки), во вторую — 4. Мы видим, что при копировании одной строки AnsiString в другую символ #0 в середине строки — не помеха (вызов UniqueString добавлен для того, чтобы обеспечить реальное копирование строки, а не только копирование указателя). А вот как только мы превращаем эту строку в PChar, информация о её истинной длине теряется, и при обратном преобразовании компилятор ориентируется на символ #0, и строка обрубается.

Потеря куска строки после символа #0 происходит всегда, когда есть преобразование ShortString или AnsiString в PChar, даже неявное. Например, все API-функции работают с нуль-терминированными строками, а визуальные компоненты — просто обёртки над этими функциями, поэтому вывести с их помощью на экран строку, содержащую #0, целиком невозможно.

Но главный подводный камень, связанный с символом #0 в середине строки, заключается в том, что целый ряд стандартных функций для работы со строками AnsiString на самом деле используют API-функции (или даже библиотечные функции Delphi, предназначенные для работы с PChar), что приводит к игнорированию «хвоста» после #0. Следующий код (пример ZeroFind) иллюстрирует эту проблему:
Хотя символ «Z» присутствует в строке, в которой производится поиск, на экран будет выеден «0», что означает отсутствие искомой подстроки. Это связано с тем, что функция AnsiPos использует функции StrPos и CompareString, предназначенные для работы со строками PChar, поэтому поиск за символом #0 не производится. Если заменить в этом примере функцию AnsiPos на Pos, которая работает с типом AnsiString должным образом, на экран будет выведено правильное значение «3».

Описанные проблемы заставляют очень осторожно относиться к использованию символа #0 в середине строк AnsiString — это может стать источником неожиданных проблем.

Строки в записях

Поля в записях могут иметь любой строковый тип без дополнительных ограничений. Однако следует учитывать, что, в отличие от полей простых типов, значения полей типа PChar и AnsiString лежат вне пределов структуры, причем в случае AnsiString это не так бросается в глаза, т.к. вручную выделять и освобождать память не приходится. Это может подложить неприятный сюрприз, если работать со структурой как цельным блоком данных. Чаще всего проблема появляется при записи структуры в поток, файл и т.п. В этом случае записывается только значение указателя, которое не имеет никакого смысла для того, кто потом эти данные читает: такой указатель указывает либо в никуда, либо на данные, никакого отношения к строке не имеющие.

Для иллюстрации этой проблемы, а также методов её решения нам понадобятся два проекта: RecordRead и RecordWrite (лежат в папке RecordReadWrite). Обойтись одним проектом здесь нельзя — указатель, переданный в пределах проекта, остаётся корректным, поэтому проблема маскируется. В проекте RecordWrite три кнопки, соответствующие трём методам сохранения записи в поток TFileStream (в файлы Method1.stm, Method2.stm и Method3.stm соответственно). В три целочисленных поля заносятся текущие час, минута, секунда и сотая доля секунды, строка — произвольная, введённая пользователем в Edit1. Файлы пишутся в текущую папку. В проекте RecordRead три кнопки соответствуют трём методам чтения (каждый — из своего файла). Сначала рассмотрим первый метод — как делать ни в коем случае нельзя.

В проекте RecordWrite:
В проекте RecordRead:
Примечание: В проекте RecordRead объявлена такая же запись TMethod1Record, описание которой во втором случае для краткости опущено.

Запись в файл происходит нормально, но при чтении в строке, отмеченной звёздочкой, скорее всего, возникает исключение Access violation (в некоторых случаях исключения может не быть, но вместо сообщения будет выведен мусор). Причину этого мы уже обсудили выше — указатель Msg, действительный в контексте процесса RecordWrite, не имеет смысла в процессе RecordRead, а сама строка передана не была. Без ошибок этим методом можно передать только пустую строку, потому что пустой строке соответствует указатель nil, имеющий одинаковый смысл во всех процессах. Однако метод передачи строк, умеющий передавать только пустые строки, имеет весьма сомнительную ценность с практической точки зрения.

Самый простой способ исправить ситуацию — изменить тип поля Msg на ShortString. Больше ничего в приведённом коде менять не придётся. Однако использование ShortString имеет два недостатка. Во-первых, длина строки в этом случае ограничена 255-ю символами. Во-вторых, если длина строки меньше максимально возможной, часть памяти, выделенной для структуры, не будет использована. Если средняя длина строки существенно меньше максимальной, то таких неиспользуемых кусков в потоке будет много, т.е. файл окажется неоправданно раздут. Это всегда плохо, а в некоторых случаях — вообще недопустимо, поэтому ShortString можно посоветовать только в тех случаях, когда строки имеют примерно одинаковую длину (напомним, что ShortString позволяет ограничить длину строки меньшим, чем 255, числом символов — в этом случае поле будет занимать меньше места).

С одним из этих недостатков можно бороться: если использовать в записи не ShortString, а статический массив типа Char, можно передавать строки большей, чем 255 символов, длины. Второй метод демонстрирует этот способ.

В проекте RecordWrite:
В проекте RecordRead:
Константа MsgLen задаёт максимальную (вместе с завершающим нулём) длину строки. В приведённом примере она взята достаточно маленькой, чтобы наглядно продемонстрировать, что данный метод имеет ограничения на длину строки. Переделки по сравнению с кодом предыдущего метода минимальны: при записи для копирования значения Edit1.Text вместо присваивания нужно использовать функцию StrPLCopy. В коде RecordRead изменений (за исключением описания самой структуры) вообще нет — это достигается за счёт того, что массив Char считается компилятором совместимым с PChar, а выражения типа PChar могут быть присвоены переменным типа AnsiString — конвертирование выполнится автоматически.

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

В проекте RecordWrite:
В проекте RecordRead:
Наконец-то мы получили код, который безошибочно передаёт строку, не имея при этом ограничений длины (кроме ограничения на длину AnsiString) и не расходуя понапрасну память. Правда, сам код получился сложнее. Во-первых, из записи исключено поле типа string, и теперь эту запись можно без проблем читать и писать в поток. Во-вторых, в поток после неё записывается длина строки. В-третьих — сама строка.

Параметры вызова методов ReadBuffer и WriteBuffer для чтения/записи строки требуют дополнительного комментария. Метод WriteBuffer пишет в поток ту область памяти, которую занимает указанный в качестве первого параметра объект. Если бы мы указали саму переменную Msg, то записалась бы та часть памяти, которую занимает эта переменная, т.е. сам указатель. А нам не нужен указатель, нам нужна та область памяти, на которую он указывает, поэтому указатель нужно разыменовать с помощью оператора «^». Но просто взять и применить этот оператор к переменной Msg нельзя — с точки зрения синтаксиса она не является указателем. Поэтому приходится сначала приводить её к указателю (здесь подошёл бы любой указатель, не обязательно нетипизированный). То же самое относится и к ReadBuffer: чтобы прочитанные данные укладывались не туда, где хранится указатель на строку, а туда, где хранится сама строка, приходится использовать такую же конструкцию. И обратите внимание, что прежде чем читать строку, нужно зарезервировать для неё память с помощью SetLength.

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

Примечание: Если сделать MsgLen не независимой переменной, а полем записи, можно сэкономить на одном вызове ReadBuffer и WriteBuffer.

Недостатком этого метода является то, что мы вынуждены переделывать под него запись. В нашем примере это не составило проблемы, но в реальных проектах запись обычно используется не только для чтения и сохранения в поток, и если взять и выкинуть из неё строки, то все прочие участки кода станут более громоздкими. Поэтому в реальности приходится писать отдельные процедуры, которые сохраняют запись не как единое целое, а по отдельным полям.

Выше мы говорили о том, что копирование записей, содержащих поля типа AnsiString, в рамках одного процесса маскирует проблему, т.к. указатель остаётся допустимым и даже (какое-то время) правильным. Но сейчас с помощью приведённого ниже кода (пример RecordCopy) мы увидим, что проблема не исчезает, а просто становится менее заметной.
На экране вместо ожидаемого «Hello. » появится «Good bye». Это происходит вот почему: процедура Move осуществляет простое побайтное копирование одной области памяти в другую, механизм изменения счётчика ссылок при этом не срабатывает. В результате менеджер памяти не будет знать, что после завершения локальной процедуры CopyRecord остаются ссылки на строку «Hello. «. Память, выделенная этой строке, освобождается. Но Rec.Str продолжает ссылаться на эту уже освобождённую память. Для строки S выделяется свободная память — та самая, где раньше была строка LocalRec.Str. А так как Rec.Str продолжает ссылаться на эту область памяти, поэтому обращение к ней даёт строку «Good bye», которая теперь там размещена.

Обратите внимание на последнюю строку — приведение Rec.Str к типу Pointer и обнулению. Это сделано для того, чтобы менеджер памяти не пытался финализировать строку Rec.Str после завершения процедуры, иначе он попытается освободить память, которая уже освобождена, и возникнет ошибка.

Чтобы показать, насколько коварна эта ошибка, рассмотрим следующий код (из того же примера):
От предыдущего случая он отличается только тем, что в нём нет вызовов UniqueString, и строки указывают на константы в сегменте кода, которые никогда не удаляются. На экране получаем вполне ожидаемое «Привет!». Обнулять указатель здесь уже нет смысла, потому что освобождать константу менеджер памяти не будет. Так ошибка оказалась скрытой.

Продолжим наши эксперименты. Запустим пример RecordCopy и понажимаем попеременно кнопки Button1 и Button2. Мы видим, что результат не зависит от порядка, в котором мы нажимаем кнопки.

Модифицируем код: в локальной процедуре обработчика Button1Click уберём из строки «Hello. » восклицательные знаки, сократив её до «Hello». Теперь можно наблюдать интересный эффект: если после запуска нажать сначала Button1, то никаких изменений мы не заметим. А вот если кнопка Button2 будет нажата раньше, чем Button1, то при последующих нажатиях Button1 никаких видимых эффектов не будет. Это связано с тем, что теперь строка «Hello» не равна по длине строке «Good bye», поэтому разместится ли «Good bye» в том же месте памяти, где раньше была «Hello», или в каком-то другом, зависит от истории выделения и освобождения памяти. Если мы начинаем «с чистого листа», память после строки «Hello» остаётся свободной, поэтому туда можно поставить более длинную строку. А вот если раньше память уже выделялась и освобождалась (внутри методов TLabel), то тот кусочек свободной памяти, который достаточен для «Hello», слишком мал для «Good bye», и эта строка размещается в другом месте. А там, куда указывает Rec.Str, остаётся мусор, работать с которым нормально невозможно, поэтому при попытке присвоить это свойству Label1.Caption последнее не меняется.

Примечание: Если увеличить длину строки «Привет!» хотя бы на один символ, чтобы она была не короче, чем «Good bye» (или наоборот, сократить его так, чтобы оно стало короче «Hello»), мы снова увидим, что порядок нажатия кнопок не влияет на результат. Это происходит потому, что строка «Hello» размещается там, где раньше была строка «Привет!», а вот «Good bye» там уже не помещается. Если же обе строки там помещаются (или обе не помещаются), они снова оказываются в одной области памяти. Внимательный читатель может спросить: а при чём здесь длина строки «Привет!», если эта строка хранится в сегменте кода и никогда не освобождается? Дело в том, что когда мы присваиваем эту строку свойству Label1.Caption, внутри методов TLabel происходит её перенос в динамическую память для внутренних нужд этого класса.

Даже на таком простом примере видно, насколько коварна эта ошибка и как незначительные изменения в коде могут кардинально изменить её проявления. Между тем приведённый здесь код — плод долгого «приручения» этой ошибки, чтобы она всегда проявлялась предсказуемым образом. Но даже сейчас мы не можем дать полной гарантии, что у кого-то из читателей из-за какой-то неучтённой мелочи не возникнет ситуация, когда эта ошибка проявляется как-то по-другому (хотя вероятность этого мала). В реальных проектах всё гораздо сложнее, и поведение программы из-за этой ошибки может стать таким неожиданным, а проявление этой ошибки — настолько далёким от того места, где она сделана, что впору будет прыгать вокруг компьютера с бубном, изгоняя бесов. Чтобы не оказаться в таком положении, нужно очень аккуратно работать со строками (а также с другими автоматически финализируемыми типами: динамическими массивами, интерфейсами, вариантами), чтобы тот код, который неявно генерирует компилятор, не оказался в тупике. Чаще всего проблемы возникают при побайтном копировании переменной типа AnsiString (не обязательно в составе записи) или работе с ней как с указателем другого типа. Это не значит, что приводить AnsiString к другим указателям категорически нельзя — выше мы уже делали это, и вполне успешно. Но применяя любой низкоуровневый механизм к таким строкам, разработчик должен чётко представлять, как это отразится на внутренних механизмах работы с ними. Иначе — вот такая непонятная ошибка.

Использование ShareMem

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

Итак, создаём новую динамически компонуемую библиотеку (DLL). Delphi делает нам следующую заготовку:
Самое важное здесь — комментарий. Его следует внимательно прочитать и осознать, а главное — выполнить эти советы, иначе при передаче строк AnsiString между DLL и программой вы будете получать Access violation в самых неожиданных местах. Почему-то многие им пренебрегают, а потом бегут с вопросами в разные форумы, хотя минимум внимательности и отсутствие снобизма по отношению «к этим, из Borland’а, которые навставляли тут никому не нужных комментариев» могли бы уберечь от ошибки.

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

Менеджер памяти реализуется модулем System. Так как DLL компонуется отдельно от использующего её exe-файла, у неё будет своя копия кода System и, следовательно, свой менеджер памяти. И если объект, память для которого была выделена в коде основного модуля программы, попытаться освободить в коде DLL, получится, что освобождать память будет совсем не тот менеджер, который её выделил. А сделать он этого не сможет, т.к. не обладает информацией о выделенном блоке. Результат — ошибка (скорее всего, Access violation при выходе из процедуры). А при работе со строками AnsiString память постоянно выделяется и освобождается, поэтому, попытавшись работать с одной и той же строкой и в главном модуле, и в DLL, мы получим ошибку.

Теперь, когда мы поняли, почему возникает проблема, разберёмся, как ShareMem её решает. Delphi предоставляет возможность заменить стандартный менеджер памяти своим: для этого нужно написать функции выделения, освобождения и перераспределения памяти и сообщить их адреса через процедуру SetMemoryManager — после этого все функции для работы с динамической памятью будут работать через эти функции. Именно это и делает ShareMem: в секции инициализации этого модуля содержится код, заменяющий функции работы с памятью своими, причём эти функции находятся во внешней библиотеке BORLNDMM.DLL. Получается, что и библиотека, и главный модуль работают с одним менеджером памяти, что решает описанные выше проблемы.

Если менеджер памяти попытаться поменять не в самом начале программы, ему придётся освобождать память, которую успел выделить предыдущий менеджер памяти, что приведёт к той же самой проблеме. Поэтому заменить менеджер памяти нужно до того, как будет выполнена первая операция по её выделению. Отсюда возникает требование вставлять ShareMem первым модулем в dpr-файлах главного модуля и DLL — чтобы его секция инициализации была первым выполняемым программой кодом.

Кстати, к совету использовать вместо AnsiString PChar, чтобы избавиться от необходимости использования ShareMem, данному в комментарии, следует относится осторожно: если мы попытаемся, например, вызвать StrNew в основной программе, а StrDispose — в DLL, то получим ту же проблему. Вопрос не в типах данных, а в том, как манипулировать памятью.

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

Следует также упомянуть о ещё одной альтернативе передачи строк в DLL — типе WideString. Этот тип хранит строку в кодировке Unicode и является, по сути, обёрткой над системным типом BSTR. Работать с WideString так же просто, как и с AnsiString, перекодирование из ANSI в Unicode и обратно выполняется автоматически при присваивании значения одного типа переменной другого. В целях совместимости с COM и OLE для работы с памятью для строк WideString используется специальный системный менеджер памяти (через API-функции SysAllocString, SysFreeString и т.п.), поэтому передавать эти строки из DLL в главный модуль и обратно можно совершенно безопасно даже без ShareMem. Правда, при этом не стоит забывать о расходовании процессорного времени на перекодировку, если основная работа идёт не с Unicode, а с ANSI.

Отметим одну ошибку, которую делают новички, прочитавшие комментарий про ShareMem, но не умеющие работать с PChar. Они пишут, например, такой код для функции, находящейся в DLL и возвращающей строку:
Такой код компилируется и даже, за редким исключением, даёт ожидаемый результат. Но тем не менее, в этом коде грубая ошибка. Указатель, возвращаемый функцией, указывает на область памяти, которая считается свободной — после того как переменная S вышла за пределы области видимости, память, которую занимала эта строка, освободилась. Менеджер памяти может в любой момент вернуть эту память системе (тогда обращение к ней вызовет Access violation) или задействовать для других целей (тогда новая информация перетрёт содержащуюся там строку). Проблема маскируется тем, что обычно результат используется немедленно, до того как менеджер памяти что-то сделает с этим блоком. Тем не менее, полагаться на это и писать такой код не стоит.

Под использованием PChar в комментарии имеется ввиду использование его таким образом, как он используется в API-функциях: программа выделяет память для буфера, указатель на этот буфер передаёт в DLL как PChar, а DLL только заносит в этот буфер требуемое значение.

Работа со строками в Delphi 10.1 Berlin

Автор: Alex. Опубликовано в Программирование 31 Январь 2020 . просмотров: 23129

Для работы со строками в последних версиях Delphi разработчикам доступно большое количество функций, помимо которых ещё есть помощники для работы со строками, такие как TStringHelper, TStringBuilder и TRegEx. Во всём этом разнообразии бывает сложно найти нужную функцию. Я попытался разобраться, что есть в Delphi 10.1 Berlin для работы со строками и как этим всем пользоваться.

Итак, прежде чем начнём разбираться с функциями, замечу, что начиная с Delphi XE3, появился помощник TStringHelper, и теперь работать со строками можно как с записями. Т.е., если вы определили переменную со строкой (на картинке снизу – это myStr), то вы можете поставить точку и посмотреть, какие функции доступны. Это очень удобно.

Кстати аналогичные помощники появились и для работы с типами Single, Double и Extended: TSingleHelper, TDoubleHelper и TExtendedHelper.

Ну и конечно, помимо помощника TStringHelper, никуда не делся класс TStringBuilder, который используется для работы со строкой как с массивом, и который является полностью совместимым с .NET классом StringBuilder.

А для работы с текстовыми документами незаменимым окажется класс TRegEx, который является обёрткой над библиотекой PCRE, позволяющий использовать регулярные выражения для поиска, замены подстрок и расщепления текста на части.

Все приведённые в статье примеры сделаны с помощью Delphi 10.1 Berlin, поэтому в других версиях Delphi их работа не гарантируется.

Вот основные моменты, которые мы рассмотрим в статье:

Строки в Delphi

В последних версиях Delphi тип string, обозначающий строку, является псевдонимом встроенного типа System.UnicodeString. Т.е. когда вы объявляете переменную str: string, то автоматически вы объявляете переменную типа UnicodeString.

Кстати, на платформе Win32 вы можете использовать директиву « », которая превратит тип string в ShortString. С помощью этого способа вы можете использовать старый 16-битный код Delphi или Turbo Pascal в ваших проектах.

Обратите внимание, что кроме типа UnicodeString и ShortString в Delphi есть и другие типы строк, такие как AnsiString и WideString, однако дальше в статье мы будем рассматривать только работу со строками типа string.

Более глубокое изучение строк в Delphi вы можете начать с прочтения документации здесь.

Инициализация строк

Конечно, начнём мы с инициализации строк. Итак, рассмотрим объявление переменной с типом string.

В этой строчке кода мы объявляем переменную s с типом string, т.е., как было написано выше, по умолчанию с типом UnicodeString. Объявленные переменные с типом UnicodeString, в которые не присвоено значение, всегда гарантированно содержат строку нулевой длины. Чтобы теперь в переменной s была нужная нам строка, нужно просто присвоить переменной другое значение, например:

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

Изменение регистра

Для изменения регистра строк в Delphi есть функции LowerCase, UpperCase, TStringHelper.ToLower, TStringHelper.ToUpper, TStringHelper.ToLowerInvariant и TStringHelper.ToUpperInvariant. В нижний регистр строки меняют функции LowerCase, TStringHelper.ToLower и TStringHelper.ToLowerInvariant, остальные – в верхний. Обратите внимание, что функции LowerCase и UpperCase не работают с кириллицей. Функции TStringHelper.ToUpperInvariant и TStringHelper.ToLowerInvariant всегда работают независимо от текущей пользовательской локали. Вот примеры использования функций:

Конкатенация строк

Здесь конечно самый простой вариант – это использование оператора +. Но есть и другие варианты, например, функция Concat. А если вам нужно в цикле добавлять в конец одной строки большое количество других строк, то здесь пригодится метод Append класса TStringBuilder. Вот пример использования перечисленных способов:

Во всех четырёх переменных, после выполнения нашей программы, будет следующая строка: «Абвгдеёжзиклмнопрст». Четвёртый способ выглядит более громоздким, но у такого способа есть три преимущества. Во-первых, при большом количестве конкатенаций этот способ даст выигрыш по времени по сравнению с первыми тремя способами. Во-вторых, при создании объекта TStringBuilder вы сразу можете задать нужный размер массива для хранения строки, если он конечно известен. Это тоже даст выигрыш по времени. В-третьих, функция Append принимает на вход не только строки, но и другие типы, такие как Integer и Single, автоматически преобразуя их в строку.

Третий способ удобно использовать, если нужно сложить строки, находящиеся в массиве или списке. К тому же здесь первым параметром можно задать строку-разделитель, которая будет вставлена между строками, взятыми из массива. Вот пример, в котором формируется строка со списком городов, разделённых запятыми:

В результате выполнения этой функции получится строка «Москва, Санкт-Петербург, Севастополь».

Вставка подстроки в строку

Для того чтобы вставить внутрь строки подстроку вы можете использовать процедуру Insert или функцию TStringHelper.Insert. У класса TStringBuilder тоже есть аналогичная функция. Кстати, функция TStringBuilder.Insert, кроме строк умеет вставлять и другие типы, такие как Integer и Single, автоматически преобразуя их в строку. Вот пример использования:

Обратите внимание, в процедуре Insert нумерация символов начинается с 1, а в функциях TStringHelper.Insert и TStringBuilder.Insert – с 0. Все приведённые способы меняют строку, хранящуюся в переменной.

Удаление части строки

Допустим, вам нужно удалить из строки часть символов. Здесь нам помогут процедура Delete и функция TStringHelper.Remove. У класса TStringBuilder тоже есть функция Remove. Вот примеры использования:

Во всех трёх способах из строки «Абвгд» получится строка «Агд». Обратите внимание, что в процедуре Delete нумерация символов начинается с 1, а в функциях Remove – с 0.

Также интересно, что функция TStringHelper.Remove не трогает исходную строку. Вместо этого она возвращает новую строку с удалёнными символами. Именно поэтому мы присваиваем результат обратно в переменную. Процедура Delete работает по-другому: она меняет исходную строку.

Помимо приведённых здесь вариантов, для удаления части строки можно использовать функции замены подстроки, просто для этого искомая подстрока заменяется на пустую, например, StringReplace(str1, substr1, »).

Копирование части строки

Здесь идёт речь о том, что часть длиной строки нужно скопировать в новую строку или массив символов. Для этого в Delphi есть функции LeftStr, RightStr, Copy, TStringHelper.Substring и TStringHelper.CopyTo. А в классе TStringBuilder – только функция CopyTo. Есть также функция MidStr в юните System.StrUtils, которая работает аналогично функции Copy, поэтому в примере её не будет.

Первые два способа копируют часть строки слева (функция LeftStr) или справа (RightStr). Остальные четыре способа подходят, как для копирования части строки слева или справа, так и из середины.

В способах 3-6 из примера мы получим сроку «вгд» или массив [‘в’, ‘г’, ‘д’]. Обратите внимание, что в функциях Copy и MidStr нумерация символов начинается с 1, а во всех остальных с 0. Исходная строка или массив символов во всех четырёх способах не меняется.

Сравнение строк

Конечно, сравнивать строки можно с помощью операторов =, , >= и <>. Но кроме этого существуют ещё много функций: StrComp, StrIComp, StrLComp, StrLIComp, CompareStr, CompareText, TStringHelper.Compare, TStringHelper.CompareOrdinal, TStringHelper.CompareTo, TStringHelper.CompareText, SameStr, SameText, TStringHelper.Equals и TStringBuilder.Equals. Функции SameText, StrIComp, CompareText, TStringHelper.CompareText и TStringHelper.Compare умеют производить регистронезависимое сравнение строк, остальные функции и операторы — регистрозависимое.

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

Самая продвинутая здесь функция – это TStringHelper.Compare. С помощью неё можно сравнивать не только целые строки, но и части строк. Здесь можно настроить зависимость от регистра, включить игнорирование символов и знаков препинания или сравнение цифр как чисел и т.д.

Операторы, а также функции TStringHelper.Equals и TStringBuilder.Equals, в результате сравнения, отдадут вам True, если условие верно, и False, если условие не верно. Функции CompareStr, CompareText, TStringHelper.Compare, TStringHelper.CompareTo, TStringHelper.CompareOrdinal и TStringHelper.CompareText работают по-другому. Они сравнивают строки с точки зрения сортировки. Функции возвращают отрицательное число, если строка, указанная в первом параметре, сортируется до строки, указанной во втором параметре, положительное число — если первая строка сортируется после второй и 0 – если строки равны.

Функции SameStr, SameText, TStringHelper.Equals и TStringBuilder.Equals сравнивают строки на соответствие.

Итак, вот примеры использования вышеперечисленных функций и операторов:

Поиск подстроки в строке

Теперь давайте посмотрим, как можно найти подстроку (определённую последовательность символов) в строке. Здесь у вас есть большой выбор функций, которые возвращают либо индекс найденной подстроки, либо true или false в зависимости от того, найдена подстрока в строке или нет. Итак, давайте перечислим все функции для поиска подстроки:

В первую очередь – это функция Pos, которая ищет подстроку, начиная с указанного номера символа. Функция осуществляет регистрозависимый поиск. Здесь нумерация символов начинается с 1. Если подстрока найдена, то возвращается номер первого символа найденной подстроки, иначе – 0. Есть также функция PosEx (в юните System.StrUtils), которая работает абсолютно также. Вот пример использования функции Pos:

Аналогично функции Pos работают и функции IndexOf и LastIndexOf помощника TStringHelper. Они также осуществляют регистрозависимый поиск. Функция IndexOf ищет подстроку (или символ) с начала и до конца строки, а функция LasIndexOf – наоборот, т.е. с конца и до начала. Если подстрока найдена, то функции возвращают индекс первого символа найденной подстроки в строке. Здесь нумерация символов начинается с 0. Если подстрока не найдена, то функции возвращают -1. Также при поиске вы можете задать начало и интервал поиска. Вот примеры использования этих функций:

Теперь рассмотрим функции для проверки, есть ли подстрока в строке, и не важно, в каком месте. Для этого есть функции ContainsStr и ContainsText в юните System.StrUtils, а также функция Contains в помощнике TStringHelper.Contains. Функции ContainsStr и TStringHelper.Contains – регистрозависимые, а функция ContainsText – нет. Вот примеры использования этих функций:

Дополнительно есть функции проверяющие, наличие определённой подстроки в начале или в конце текста. Это функции StartsStr, StartsText, EndsStr и EndsText в юните System.StrUtils, а также функции StartsWith, EndsWith и EndsText у помощника TStringHelper. Функции StartsStr и EndsStr регистрозависимые, функции StartsText, EndsText и TStringHelper.EndsText регистронезависимые, а у функций TStringHelper.StartsWith и TStringHelper.EndsWith есть второй параметр для выбора режима поиска. Учтите, что регистронезависимый поиск в функции TStringHelper.StartsWith работает только с буквами латинского алфавита. По умолчанию поиск в функциях TStringHelper.StartsWith и TStringHelper.EndsWith регистрозависимый.

Обратите внимание, что регистронезависимый поиск в функциях StartsText, EndsText и TStringHelper.EndsText и TStringHelper.EndsWith ведётся для текущей локали. Т.е. если на компьютере будет установлена английская локаль, то регистронезависимый поиск по русскому тексту работать не будет.

Вот примеры использования функций:

И конечно самые продвинутые условия для поиска подстрок можно задавать при помощи регулярных выражений. Для этого есть функции TRegEx.Match и TRegEx.Matches. Вот несколько примеров использования этих функций:

Примеры и описание регулярных выражений смотрите на сайте библиотеки PCRE.

Поиск символов в строке

Случается, что нужно найти определённые символы в строке. Конечно, для этого вы можете воспользоваться функциями для поиска подстроки, о которых было написано выше, но есть и специальные функции, позволяющие найти первый попавшийся в строке символ из нескольких искомых. Это функции помощника TStringHelper: IndexOfAny, IndexOfAnyUnquoted и LastIndexOfAny. Функции IndexOfAny и IndexOfAnyUnquoted ищут, перебирая символы сначала до конца строки, а функция LastIndexOfAny – наоборот. Во всех функциях можно указать интервал поиска. Функция IndexOfAnyUnquoted умеет игнорировать символы, заключенные в кавычки, скобки и т.п. Вот пример использования этих функций:

Замена подстроки в строке

Для поиска и замены подстроки (или символа) в строке можно использовать функции StringReplace, ReplaceStr и ReplaceText, TStringHelper.Replace, TStringBuilder.Replace и TRegEx.Replace. Функции ReplaceStr и TStringBuilder.Replace – регистрозависимые, функция ReplaceText – регистронезависимая, в функциях StringReplace, TStringHelper.Replace и TRegEx.Replace зависимость от регистра настраивается флажком rfIgnoreCase. Функции TRegEx.Replace ищут подстроку, используя регулярные выражения. В функции TStringBuilder.Replace можно задать диапазон поиска подстроки. Вот примеры использования этих функций:

Обрезка пробелов и управляющих символов

Для такой часто встречающейся операции, как удаление пробелов и управляющих символов в начале и в конце строки, есть несколько функций: Trim, TrimLeft, TrimRight, TStringHelper.Trim, TStringHelper.TrimLeft и TStringHelper.TrimRight. При вызове функций TStringHelper.Trim, TStringHelper.TrimLeft и TStringHelper.TrimRight вы можете перечислить, какие символы нужно удалять. Вот пример использования этих функций:

Выравнивание текста за счёт установки пробелов

И напоследок ещё пара интересных функций, которые умеют дополнять строку пробелами или другими символами, пока она не станет нужной длины. Это функции TStringHelper.PadLeft и TStringHelper.PadRight. С помощью этих функций, например, для лучшего восприятия можно добавить пробелы в начало чисел, которые вы выдаёте столбиком в консоли или дополнить числа ведущими нулями. Вот пример использования этих функций:

Вместо заключения

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

AnsiReverseString — Функция Delphi

В данной статье рассматриваются новые функции библиотеки Tiburon Runtime Library, которые помогают обрабатывать строки Unicode.

Введение

В Части I было показано, какие выгоды дает разработчикам на Delphi поддержка кодировки Unicode, позволяя работать со всеми наборами символов в кодировке Unicode. Были рассмотрены основные особенности типа строки UnicodeString и было показано, как его можно использовать в Delphi.

В части II будет рассказано о некоторых новых функциях библиотеки Delphi Runtime Library, предназначенных для поддержки Unicode, и общих методах обработки строк.

Класс TCharacter

Библиотека Tiburon RTL включает новый класс TCharacter, который описывается в модуле Character. Это закрытый класс, который полностью состоит из статичных функций класса. Разработчикам не следует создавать экземпляры класса TCharacter, предпочтительнее просто вызывать его статические методы класса напрямую. Функции этого класса позволяют, в числе прочего, выполнять:

  • преобразование символов в верхний или нижний регистр;
  • определение типа данного символа, то есть является ли символ буквой, цифрой, знаком препинания и так далее.

Классом TCharacter используются стандарты, установленные организацией Unicode consortium.

Разработчики могут использовать класс TCharacter для выполнения многих действий, выполнявшихся до этого с наборами символов. Например, следующий программный код:

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

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

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

Класс TEncoding

Библиотека Tiburon RTL также включает новый класс TEncoding. Его назначение — определить конкретный тип кодировки символов, чтобы можно было сообщить библиотеке VCL, какой тип кодировки необходимо использовать в конкретных ситуациях.

Например, пусть есть экземпляр TStringList, содержащий текст, который необходимо записать в файл. Ранее необходимо было бы написать

и файл записался бы, по умолчанию, в кодировке ANSI. Данный код по-прежнему хорошо работает — файл запишется им в кодировке ANSI, как и раньше, но теперь, когда в Delphi поддерживаются строковые данные в кодировке Unicode, разработчикам может потребоваться записать строковые данные в какой-либо конкретной кодировке. Поэтому оператор SaveToFile (а также LoadFromFile) теперь имеет дополнительный второй параметр, которым определяется используемая кодировка:

При выполнении приведенного выше кода файл будет записан как текстовый файл в кодировке Unicode (UTF-16).

Классом TEncoding будет также преобразовываться заданный набор байтов из одной кодировки в другую, извлекаться информация о байтах и/или символах в заданной строке или массиве символов, преобразовываться любая строка в массив array of byte (TBytes) и выполняться другие функции, которые могут потребоваться для конкретной кодировки заданной строки или заданного массива символов.

Класс TEncoding включает следующие свойства класса, дающие доступ к экземпляру TEncoding заданной кодировки:

Свойство Default ссылается на активную кодовую страницу ANSI. Свойство Unicode ссылается на UTF-16.

Класс TEncoding также включает функцию

которая будет возвращать экземпляр TEncoding, соответствующий кодовой странице, переданной в параметре.

Кроме того, он включает следующую функцию:

которая будет возвращать правильный маркер порядка байтов для заданной кодировки.

Класс TEncoding также представляет собой интерфейс, совместимый с классом .Net Encoding.

TStringBuilder

Библиотека RTL теперь включает класс TStringBuilder. Его назначение ясно из его названия — это класс, предназначенный для создания строк. Класс TStringBuilder содержит большое количество перегружаемых функций для добавления, замены и вставки содержимого в заданную строку. Этот класс упрощает создание единых строк из множества различных типов данных. Каждая из функций Append, Insert и Replace возвращает экземпляр класса TStringBuilder, поэтому их можно легко объединять для создания единой строки.

Например, можно использовать класс TStringBuilder вместо усложненного оператора Format. Например, можно написать следующий программный код:

Класс TStringBuilder также представляет собой интерфейс, совместимый с классом .Net StringBuilder.

Объявление новых типов строк

Компилятор Tiburon позволяет объявить собственный тип строки, связанный с заданной кодовой страницей. Доступно любое число кодовых страниц. Например, если необходим тип строки, соответствующий кодировке ANSI-кириллице, можно объявить:

И новый тип строки будет соответствовать кодовой странице кириллицы.

Дополнительная поддержка Unicodeбиблиотекой RTL

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

StringElementSize

Подпрограммой StringElementSize возвращается типичный размер элемента (элемента кода) в заданной строке. Рассмотрим следующий код:

Результатом выполнения приведенного выше кода будет:

StringCodePage

Подпрограммой StringCodePage будет возвращаться значение Word, которое соответствует кодовой странице для заданной строки.

Рассмотрим следующий код:

Результатом выполнения приведенного выше кода будет:

Другие функции библиотеки RTL, связанные с кодировкой Unicode

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

  • UnicodeStringToUCS4String
  • UCS4StringToUnicodeString
  • UnicodeToUtf8
  • Utf8ToUnicode

Кроме того, в библиотеке RTL также появился тип RawByteString, который представляет собой тип строки, не связанный ни с какой кодировкой:

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

Как правило, присвоения типов строк совместимы друг с другом.

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

Однако некоторые преобразования могут привести к потере данных, и необходимо остерегаться при перемещении из строки одного типа, которая включает данные в кодировке Unicode, в другую, тип которой не поддерживает Unicode. Например, можно присваивать тип UnicodeString строке типа AnsiString, но если строка типа UnicodeString содержит символы, которые не отображаются в активной в данный момент кодовой странице ANSI, то эти символы будут потеряны при преобразовании. Рассмотрим следующий код:

Результат выполнения этого кода, если текущая кодовая страница ОС — 1252, будет следующим:

Как можно видеть, при назначении типа UnicodeString строке типа AnsiString информация теряется, так как символы кириллицы не отображаются кодовой страницей Windows-1252. Причина такого результата — строка UnicodeString содержала символы, не представимые в кодовой странице AnsiString, эти символы были потеряны и заменены вопросительными знаками при назначении типа UnicodeString строке типа AnsiString.

SetCodePage

Функция SetCodePage, объявленная в модуле System.pas как

представляет собой новую функцию RTL, которой задается новая кодовая страница для строки типа AnsiString. Необязательным параметром Convert определяется, следует ли преобразовать сами данные строки в заданную кодовую страницу. Если значение параметра Convert — False, то для строки просто будет изменена кодовая страница. Если значение параметра Convert — True, то данные передаваемой строки будут преобразованы в заданную кодовую страницу.

Функция SetCodePage должна использоваться редко и с большой осторожностью. Обратите внимание, что, если кодовая страница не соответствует существующим данным строки (то есть значение параметра Convert — False), то результаты могут оказаться непредсказуемыми. Кроме того, если существующие данные строки преобразованы, а в новой кодовой странице не представлен заданный исходный символ, данные могут потеряться.

Получение массива байтов TBytesиз строк

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

Заключение

Библиотека Tiburon’s Runtime Library теперь полностью поддерживает новый тип строки UnicodeString. В нее входят новые классы и подпрограммы для обработки и преобразования строк в кодировке Unicode, для управления кодовыми страницами и для упрощения перехода с более ранних версий.

В Части III будут рассмотрены специальные кодовые конструкции, которые необходимы, чтобы выяснить: готов ли тот или иной программный код к переходу на кодировку Unicode.

String. То, чего ты мог и не знать.

Ты наверное думаешь, что про строки уже и писать нечего. Тогда почитай, чего я для себя наконспектировал и наверняка что-нибудь новое для себя узнаешь. Думаю статейка будет полезна не только начинающим программистам.

Прежде всего давай четко определимся, что такое тип String в Delphi. В зависимости от директив компилятора тип String может интерпретироваться как ShortString или AnsiString.

— <$H+>или <$LongStrings On>String = AnsiString. По умолчанию.
— <$H->или <$LongStrings Off>String = ShortString.
— Можно управлять из окна настроек проекта – “Compiler” -> “Huge strings”
— Если при определении типа String указана длина строки: String[32], то вне зависимости от установок компилятора это будет означать объявление ShortString соответствующего размера.

ShortString — короткая строка

— sstr: ShortString; — это обычная паскалевская строка, то есть массив (цепочка) символов с зарезервированным в начале байтом для длины строки.
— Соответственно максимальная длина короткой строки = 0..255.
— Может объявляться так: sstr: String[макс.длина строки];.
— Если сослаться на нулевой элемент: sstr [0], то получишь длину строки в виде Char, но лучше использовать для этого Length(sstr)(она делает тоже самое). Ord(sstr[0]) = Length(sstr)
— При определении адреса короткой строки (с помощью @sstr или Addr(sstr)) возвращается указатель на байт длины.
— Индексы символов (sstr [ i]) начинается с 1.
— Значение в байте длины может быть меньше, чем размер строковой переменной : Byte(sstr[0]) ‹= SizeOf(sstr). То есть, хотя длина строки может и меняться, память, занимаемая ShortString, всегда равна 256 байтам.
— Короткие строки удобны для записи текстовой информации в файл (т.к. они фиксированной длины).

Чтобы преобразовать ShortString в PChar надо ручками добавить в конец строки терминальный нуль #0 и вернуть адрес первого символа :

AnsiString — длинная строка

— astr: AnsiString; — это длинная строка состоящая из символов AnsiChar (тоже, что и Char, пока). Этот тип принят по умолчанию, то есть если сделать определение: var astr: String; — то astr определится как AnsiString.

AnsiString можно представить в виде записи:
type // Это описательное определение,
TAnsiString = record // не предназначенное для компиляции .
RefCount: LongWord; //Счетчик ссылок
Length: LongWord; // Длина строки
Data: array[1..Length+1] of AnsiChar; //Массив символов, нумерация с единицы

Так что AnsiString — это указатель на запись, только ссылается он не на начало записи, а на начало поля Data. Ты можешь это проверить сам (Приложение 1). Объяви заголовок строки вот так:

В коде объяви строку и указатель на структуру заголовка:

Получи указатель на заголовок строки S:

P : = Pointer ( Integer ( S ) — 8 )

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

— Получить символ по его номеру (индексу) можно, обращаясь со строкой, как с динамическим массивом: astr[ i].

Delphi проверяет: попадает ли индекс в границы диапазона, как и с динамическими массивами (если включена проверка диапазона <$R+>). Но пустая длинная строка представлена нулевым указателем. Поэтому проверка границ пустой строки (при обращении к символу строки по индексу) приводит к ошибке доступа вместо ошибки выхода за границы диапазона.

По умолчанию проверка диапазона выключена ( <$R->или <$RangeChecks Off>), но лучше всегда ее включать, т.к. она помогает отловить многие ошибки, а в релизе сделает прогу менее чувствительной к BufferOverflow-атаке (т.н. строковое и массивное переполнение). По этой же причине всегда включай <$O+>или <$OverflowCheks On>. Выключай их только при серьезной проблеме с производительностью и только в критичных участках кода.

— Длина строки (Length) может изменяться с помощью функции SetLength. На настоящий момент максимальная длина длинной строки = 2 Гб (т.к. размер длины строки — 4 байта). Минимальная длина – 4 байта (пустая строка) Функция SizeOf(astr) возвратит 4 байта при любой длине строки, т.е. возвращается размер указателя.
— В конец строки автоматически записывается терминальный нуль #0 (но он не включается в общую длину строки). Поэтому строку легко преобразовать в тип PChar: PChar(astr). С короткой строкой такое преобразование не получится, потому что у нее в конце терминального нуля нет !
— AnsiString поддерживает многобайтовые строки. В отличие от PChar в AnsiString могут быть любые символы, даже несколько терминальных нулей! Но некоторые строковые фукции думают, что терминальный нуль в строке только один и что он в конце (например SysUtils.AnsiPos). Учитывай это!
— Счетчик ссылок RefCount используется в операциях присваивания и управляет жизненным циклом строки. Придуман для экономии памяти. Если мы копируем строку в другую переменную ( somestr := astr; ), то настоящего копирования памяти не происходит, а просто копируется указатель (AnsiString ведь указатель) и увеличивается на 1 счетчик ссылок. А вот если исходная строка изменяется (somestr := astr + ‘A’; ), то тогда создается новая уникальная запись со своим счетчиком ссылок.
— Если тебе очень нужно создать именно уникальную строку, а не увеличить счетчик ссылок, то используй функцию: procedure UniqueString (var str: string). Она гарантирует, что строка str до этого больше нигде не использовалась и, изменяя эту строку, ты больше нигде не напортачишь. Например может потребоваться указатель на строку PChar при работе с API-функциями. Тогда создай уникальную строку, преобразуй ее в PChar и спокойно передавай в функцию не опасаясь побочных эффектов.

Пример. Вывод русского текста в консольное окно. Это почему-то у многих вызывает трудности.

— Компилятор сам управляет длинными строками, не доверяя это программисту, и вставляет вместо операций со строками свои процедуры. Память для длинной строки выделяется динамически. Компилятор почти всегда (про почти: см. в третьей статье — Переменная Result) инициализирует длинные строки: (примерно так) Pointer(astr) := nil; При выходе из области видимости (из процедуры, при разрушении объекта) компилятор вставляет процедуру финализации и освобождения динамической памяти примерно так: System._LStrClr(S);

PChar — нультерминальная строка

— pstr: PChar; — это нультерминальная строка (zero-terminated). Так называется, потому что представляет собой указатель на цепочку символов, заканчивающуюся терминальным нулем #0. Ее еще называют сишной строкой (из языка С, там она определяется как char*).
— type PChar = ^Char;
— Используется для вызова ANSI-версий API-функций (типа CreateFileA). VCL использует только ANSI-версии API-функций для совместимости со всеми версиями Windows, поэтому вызов CreateFile идентичен CreateFileA. В модуле SysUtils сделаны функции-оболочки для многих API-функций, в которые надо передавать String вместо PChar (все-таки PChar не родной паскалевкий тип).
— Delphi хранит терминальный нуль в конце длинных и широких строк для удобства преобразования в PChar, PAnsiChar и PWideChar.
— Можно рассматривать PChar, как указатель на array of Char. В этом случае индексы начинаются с нуля. Проверка на выход за границу массива не выполняется! Подпрограмма, сканирующая строку, ищет только #0.
— При приведении AnsiString к PChar надо помнить, что Delphi автоматически уничтожает строку, когда она больше не нужна (т.е. когда счетчик ссылок равен 0, например при выходе из процедуры) , и тогда в переменной PChar может оказаться некорректный указатель. Поэтому надо быть осторожным при сохранении указателя PChar для дальнейшего использования (pstr := PChar(astr); ) , а лучше делать это приведение только при передаче параметров в API-функцию. То же относится и к приведению WideString к PWideChar. Прочитай еще про UniqueString выше.
— Операции с PChar проходят медленнее, чем операции с AnsiString, потому-что сначала Delphi сканирует всю строку PChar, что определить ее длину, а уже потом производит с ней действия.
— PChar автоматически преобразуется в AnsiString: astr := patr; , но эта операция проходит медленно.

Чтобы избежать накладных расходов можно использовать:
procedure SetString(var Str: string; Buffer: PChar; Length: Integer); устанавливает длину Str равной Length и копирует Length символов из Buffer в Str (если Str — короткая строка, то Length должна быть ‹256). Эта процедура используется в исходном коде многих строковых функций Delphi.

— PWideChar — скажем так, это «широкая» нультерминальная строка.
— Для хранения символа используется 2 байта. Поддерживает стандарт Unicode.
— На конце — терминальный нуль #0.
— Может рассматриваться как указатель на array of WideChar. Нумерация начинается с нуля. Так же как и PChar — не контролирует выход за границы массива!
— Используется для передачи параметров в Unicode-версии API-функций (типа CreateFileW), подпрограммы OLE и COM.
— Создать строку PWideChar из String можно с помощью функции StringToOleStr. Только помни, что строка создается динамически и потом надо освободить память с помощью API-функции SysFreeString.

WideString — широкая строка

— wstr: WideString; — широкая строка. Хранит строку в формате Unicode, то есть использует для хранения символа 2 байта (16-битовые символы WideChar).
— Первые 256 символов в Unicode (WideChar) и AnsiChar (Char) совпадают.
— Также, как и AnsiString, WideString отслеживает свою длину, дописывает в конец #0 (может быть преобразована в PWideChar), но не содержит счетчика ссылок, поэтому любое присваивание приводит к копированию строки в памяти.
— Delphi автоматически по мере надобности расширяет «узкие» строки и сужает «широкие».
— При приведении WideString к AnsiString используется кодовая страница ANSI. Преобразование не-ANSI-символов (с индексом больше 255) происходит, как принято в Windows по умолчанию (то есть зависит от национальных настроек). При этом приведении в строке могут оказаться многобайтовые символы. Чтобы управлять процессом преобразования надо напрямую вызывать API-функцию WideCharToMultiByte.

Многобайтовые строки — для сведения

— Многобайтовая строка — это строка, в которой символ может занимать более 1 байта (в Windows используются 2 байта). Не надо путать многобайтовые строки с Unicode — это разные вещи, хотя они приводятся друг к другу. В Unicode символ всегда занимает 2 байта, а многобайтовой строке он может занимать 1 или 2 байта (во как!).
— Нужны такие строки для некоторых национальных языков (японского, китайского), где используется больше 256 символов.
— Байт в многобайтовой строке может быть: одинарным символом, ведущим байтом (первым байтом символа) и завершающим байтом (т.е. вторым байтом). При обработке таких строк надо учитывать этот момент, т.к. символ, выглядящий как ‘A’ может оказаться вторым байтом многобайтового символа.
— Delphi не всегда корректно работает с многобайтовыми строками, и в этом случае нас опять спасает модуль SysUtils, где есть специальные функции. Для определения типа байта (по его индексу) в многобайтовой строке применяются функции SysUtils: ByteType и StrByteType:

— В реальной работе многобайтовая строка может получиться при приведении W > — Если планируется программу (или компонент) продавать (да еще за бугром), то при встрече с многобайтовыми строками надо хотя бы корректно завершить работу (конечно лучше, чтобы прога их поддерживала). Встает вопрос: Как определить нужна ли поддержка многобайтовых строк? Ответ: В переменной var SysLocale: TSysLocale; хранится информация о региональных установках Windows по умолчанию и, если поддержка многобайтовых срок нужна, то SysLocale.FarEast = True.
— Правильно обрабатывать такие строки особенно важно для имен файлов. Ты ведь собираешься со своей программой на мировой рынок выходить .

На сладкое: resourcestring

— Что ты делаешь, когда тебе надо в программу включить строковые ресурсы? Наверное создаешь файл с расширением mystringresource.rc, пишешь в него по специальным правилам свои строки, компилишь файл в res c помощью brcc32.exe, включаешь в свой экзешник директивой компилятора <$R mystringresource.res>и потом загружаешь из него строки с помошью API-функций. Скажи мне, а нужна тебе такая морока? Разработчики Delphi, как я постоянно убеждаюсь, далеко не дураки и всё уже для тебя придумали.
— Всё что требуется от тебя — это объявить с своем модуле строковую константу вот так:

resourcestring
MyResString = ‘Hello World’;

— ВСЁ! Теперь твоя строка будет сохранена в строковом табличном ресурсе, под уникальным номером. Можешь обращаться с ней, как с обычной строковой константой. После компиляции проги можешь открыть ее ResHacker’ом или Restorator’ом и среди других строк увидишь свою. Учти, что номер(идентификатор) ресурса присваивается автоматически и может меняться от компиляции к компиляции. Так что особо на него не полагайся.
— Компилятор заменяет строковую константу на вызов LoadResSring для загрузки ресурса во время выполнения программы.
— Эти ресурсные строки очень полезны, если потом надо будет локализовать программу для другого языка. Поэтому как resourcestring надо объявлять все строковые констаты в программе: сообщения об ошибках и не только, хинты-подсказки и т.п. Тоже самое и даже в большей степени относится к разработке компонентов.
— Delphi сам назначает номера строковым ресурсам, так что можешь не беспокоиться насчет конфликтов идентификаторов resourcestring из разных модулей.
— Если ресурсная строка используется в качестве строки формата (например для функции SysUtils.Format), то обязательно включай в нее спецификаторы позиций (потом удобнее переводить будет, т.к. в другом языке и порядок слов другой):

— Адрес resourcestring — указатель типа PResStringRec, который ты можешь использовать для получения идентификатора ресурса во время работы программы:

— Получить номер строкового ресурса можно так:

AnsiReverseString — Функция Delphi

цитата:
The representation of the classic AnsiString type was the following:
-8: Ref count
-4: length
String reference address: First char of string

In Delphi 2009 the representation for reference-counted strings becomes:
-12: Code page
-10: Elem size
-8: Ref count
-4: length
String reference address: First char of string

Так что вы неправы дважды — 1) что это было «в сильно раньшие времена»; 2) про ссылку на heap. Всё хранится вместе, никаких ссылок.

цитата: bislomet:
olivenoel
а какие из string (http://docwiki.embarcadero.com/RADStudio/XE3/en/String_Types) -ов Вы при этом имеете в виду?
Если речь идет о null-terminated — то они вполне себе user managable, а не automatic. И вполне могут утекать куда угодно.
Уточните!

FastMM (или кто там в ХЕ2), говорит unexpected memory leak UnicodeString x1.

Погуглила немного, оказывается такое имеет место быть, если строка является частью структуры (record). Но есть подозрение на одно место, где в массив AnsiChar присвается литеральное выражение (корое вроде по сути UnicodeString)

5. olivenoel , 26.06.2013 10:09
6. bislomet , 26.06.2013 11:46
Spirit-1
Вы заблуждаетесь. Лучше почитайте help к delphi xe3 (по ссылке, которю я давал выше)
Вы говорите об эпохе «Delphi до XE» — а это таки «сильно раньшие времена»

[pre|AnsiString represents a dynamically allocated string whose maximum length is limited only by available memory.

An AnsiString variable is a structure containing string information. When the variable is empty — that is, when it contains a zero-length string, the pointer is nil and the string uses no additional storage. When the variable is nonempty, it points to a dynamically allocated block of memory that contains the string value. This memory is allocated on the heap.
[/pre]

Добавление от 26.06.2013 11:47:

olivenoel
не путайте строку с array of char, это же не «C»

olivenoel
не путайте строку с array of char, это же не «C»

не буду Но вопрос остается, может ли «потеря» младших (старших) байтов при автоконвертации UnicodeString в AnsiString (такая конвертация тоже имеет место быть в виде

) вести к утечкам?

проблему вроде бы решила. Для одного из классов с Р. в поле и New (для поля) в конструкторе, не был переопределен деструктор, в котором должен быть Dispose для Р. -поля.

7. olivenoel , 26.06.2013 12:09
8. Spirit-1 , 26.06.2013 14:42
bislomet
Причём тут AnsiString? Я вам привёл кусок описания для нового юникодного (!) String. Обратите внимание, что на самом деле подход практически не поменялся. Добавились поля Code Page и Elem Size по сравнению со стрингами до D2009 (ссылаюсь на Канту опять же). Ну и если сравнивать с AnsiString, то, конечно, поле длинны стало четырёхбайтовым.
9. MBo , 26.06.2013 15:19
Spirit-1

Ну и если сравнивать с AnsiString, то, конечно, поле длинны стало четырёхбайтовым.

В Ansistring оно тоже было четырехбайтовым. Возможно, имелись в виду ShortString?

10. Spirit-1 , 26.06.2013 15:23
Да, насчёт длины ерунду сказал. Остальное всё в силе.
11. bislomet , 26.06.2013 15:52
Spirit-1
Вы опять-таки заблуждаетесь. Чуть дальше в том же help написано:
The UnicodeString type has exactly the same structure as the AnsiString type.

Т.е. все, что не является ShortString — устроено именно таким образом — по мнению разработчиков
12. Spirit-1 , 26.06.2013 16:14
bislomet
Попробуйте смотреть чуть дальше хэлпа от XE3.

Повторюсь, что начиная с версии D2009 эти два типа суть одно и то же, поскольку тип AnsiString определен как UnicodeString с фиксированной Code Page.
Я же сравниваю с AnsiString-ом, который был до 2009-ой Delphi. И привёл выдержку из книги — какая структура стринга была до 2009-й версии и какая стала после.

На всякий случай повторю:

цитата:
The representation of the classic AnsiString type was the following:
-8: Ref count
-4: length
String reference address: First char of string

In Delphi 2009 the representation for reference-counted strings becomes:
-12: Code page
-10: Elem size
-8: Ref count
-4: length
String reference address: First char of string

Читайте внимательно, если кроме дойча английским разумеете.

13. bislomet , 26.06.2013 17:38
Spirit-1
Повторюсь, что начиная с версии D2009 эти два типа суть одно и то же, поскольку тип AnsiString определен как UnicodeString с фиксированной Code Page.
Это утверждение никоим образом не противоречит моему и не подтверждает Ваше.
Они таки одинаковые — но обе структуры имеют указатель на heap..

Я как-то больше верю хелпу от разработчиков.
А вот с чтением проблемы скорее у Вас — иначе Вы бы заметили, что я Вам именно что английские ссылки приводил.

Добавление от 26.06.2013 17:50:

Spirit-1
таки вот что Вы имеете в виду: а вот то, что я имею в виду:Здесь: Long String Types (http://docwiki.embarcadero.com/RADStudio/XE4/en/Internal_Data_Formats)
Т.е. Вы говорите о том, как выглядит та структура, об указателе на которую говорю я

14. Imlb , 26.06.2013 19:32
bislomet
об указателе на которую говорю я

Так было всегда или с очень давних времен.

«перед строкой» было в сильно раньшие времена,

И продолжается до сих пор.

15. bislomet , 27.06.2013 08:50
Imlb
Так было всегда или с очень давних времен.

Не всегда, а только после появления в Delphi длинных строк
16. Spirit-1 , 27.06.2013 15:02
А разве в дельфях длинные строки не были с самой первой версии?
17. bislomet , 27.06.2013 15:05
Spirit-1
нет, долгое время макс. длина строки в delphi была 255 символов — то, что впоследствии стало называться ShortString. И вот там как-раз в нулевом байте хранилась длина строки.
Именно для совместимости с «теми еще» строками все нынешние строки в Delphi индексируются с 1.
18. Spirit-1 , 27.06.2013 17:37
bislomet
Посмотрел. Действительно, в первых строки были короткими. Длинные пошли со второй версии. Так что фраза «долгое время макс. длина строки в delphi была 255 символов» не соответствует действительности.
19. bislomet , 27.06.2013 17:54
Spirit-1
Не совсем так — дело в том, что тип string все еще оставался 255-байтным.
а появился длинный AnsiString, который не был синонимом string ни в коем случае!

А сам тип string перестал быть ShortString куда позже, где-то в районе 6й версии.

20. Spirit-1 , 27.06.2013 18:21
Ничего подобного. String в Delphi 2.0 стал = AnsiString, старый String (короткий) стал называться ShortString’ом.

цитата (Spirit-1): А разве в дельфях длинные строки не были с самой первой версии?

Нет, обычные борланд-паскалевской реализации (байт с индексом 0 — длинна строки).

bislomet
Меня больше интересует слово «ANSI», чем 32 битный «длинн». Анси типа означает АНСИ и не хухры-мухры ? Или же нормально в ногу со временем там реально живет UTF8 ?

А от это паноптикум.
-Труп умер 50 лет назад
-Да, но продлим ресурс его креслу еще на 1000 лет. А то и должность завхоза была вакантна.
-Только у нас ANSI завхоз в ANSI кресле.

21. vertur , 28.06.2013 03:49
22. Spirit-1 , 28.06.2013 09:52
vertur
Мы уже этот вопрос отработали . Короткие строки были в первой версии, начиная со второй пошли длинные (по дефолту).
23. AzikAtom , 05.07.2013 11:54
olivenoel

А не в этом ли дело? В помощи написано The New procedure creates a new dynamic variable and sets a pointer variable to point to it. P is a variable of any pointer type. The size of the allocated memory block corresponds to the size of the type that P points to. The newly created variable can be referenced as P^. If there isn’t enough memory available to allocate the dynamic variable, an EOutOfMemory exception is raised. For more information on handling run-time library exceptions, see Handling RTL Exceptions

When an application is finished using a dynamic variable created with New, it should dispose of the memory allocated for the variable using the Dispose standard procedure.
Т.е., при объявлении переменной типа TMyRecType память уже была выделена, а вы её ещё раз выделили и старый указатель потеряли.

24. Spirit-1 , 05.07.2013 13:13
AzikAtom
Не могу утверждать точно (проверить прямо сейчас не могу), но вроде бы если TMyRecType не является указателем, то New(FMyRec) не скомпилируется.

цитата: Spirit-1:
AzikAtom
Не могу утверждать точно (проверить прямо сейчас не могу), но вроде бы если TMyRecType не является указателем, то New(FMyRec) не скомпилируется.

скорее всего там таки был PMyRecType и я ошиблась при перепечатке кода

25. olivenoel , 05.07.2013 13:42
26. AzikAtom , 05.07.2013 14:03
Spirit-1
Написано:

Значит, это не указатель и вручную не надо память брать.

olivenoel
скорее всего там таки был PMyRecType и я ошиблась при перепечатке кода
Вот если бы у вас было:

тогда да, и NEW и последующий DISPOSE, а сейчас можно попробовать убрать этот NEW().
Правда, если вы эти переменные потом собираете в списки, то надо бы сначала сохранить указатель на самую первую переменную, которая создаётся автоматически, и затем восстановить его. Правда, тогда нет смысла объявлять саму переменную и лучше сделать изначально переменную типа ^TMyRecType.

27. CyberStorm , 05.07.2013 17:53
Утечка в Delphi может быть если используется string в структуре record.

например утечка будет:
TMyRecord=record
S:string;
End;

утечки не будет:
TMyRecord=record
S:shortstring; // или S:string[100]
End;

то же справедливо для размещенных в записях массивах,
утечка будет:
TMyRecord=record
A:array of integer;
End;

утечки не будет:
TMyRecord=record
A:array[0..1] of integer;
End;

Применение SetLength( ,0) для строковых переменных и открытых массивов записей позволяет избежать утечки памяти.

28. olivenoel , 08.07.2013 19:03
что делает Move, кроме переноса данных? Возвращет память из-под перенесенных данных менеджеру памяти?
29. AzikAtom , 08.07.2013 21:31
olivenoel
Нет. Просто копирует.
30. olivenoel , 09.07.2013 10:26
тогда MoveMemory и CopyMemory делают одно и то же? Зачем тогда две ф-ции?
31. Spirit-1 , 09.07.2013 12:15
Посмотрите здесь (http://www.delphimaster.net/view/2-1167297116) , всё станет понятно.

нет, еще больше непонятно. В частности

куда смотрит Move?

32. olivenoel , 09.07.2013 12:30
33. AzikAtom , 09.07.2013 12:44
olivenoel
тогда MoveMemory и CopyMemory делают одно и то же? Зачем тогда две ф-ции?
Видать, для удобства программистов. Щелчок правой кнопкой мыши на MoveMemory и выбрать «Find Declaration».
Открывается реализация:

Добавление от 09.07.2013 12:47:

olivenoel
Кстати, вопрос с утечками закрыт? В чём было дело?

34. olivenoel , 09.07.2013 12:53
вроде закрыт. Где-то был непарный вызов New в конструкторе (без Dispose в диструкторе). Сейчас заменила все указатели на структуры на сами структуры. Осталась где-то одна утечка Unknown x1.

Я попыталась уйти вообще от неуправляемых строк внутри библиотеки. Они есть только в теле экспортируемых ф-ций, где преобразуются в нормальные (alias) string и дальше уже работается с ними. Надо только оттестировать с ComInterop из-под C#

35. Spirit-1 , 09.07.2013 13:07
olivenoel
куда смотрит Move?
По указанной ссылке не всё истина. Фильтруйте немного. Там просто топик обсуждения. Конкретно выражение «Ну как же — процедура Move смотрит, откуда ее вызывают, в зависимости от этого
выполняет разные действия.» в той теме — полный бред, конечно.

AzikAtom
Так что, пользуйтесь move() и не парьтесь — она написана на асме.
Я бы не был столь категоричен. Там под IFDEF либо паскалевская реализация, либо на асме под x86.

36. AzikAtom , 09.07.2013 14:08
Spirit-1
В Delphi 4 такая реализация:

В Delphi 7 ещё не смотрел.

olivenoel
куда смотрит Move?
Он только смотрит, чтобы копирование при наложении было корректным. Т.е., то, что было в источнике гарантированно оказывается в приёмнике.

цитата: Spirit-1:
olivenoel
куда смотрит Move?
По указанной ссылке не всё истина. Фильтруйте немного. Там просто топик обсуждения. Конкретно выражение «Ну как же — процедура Move смотрит, откуда ее вызывают, в зависимости от этого
выполняет разные действия.» в той теме — полный бред, конечно.

Добавление от 09.07.2013 14:13:

В Delphi XE2 реализация посложнее:

37. olivenoel , 09.07.2013 14:11
38. Spirit-1 , 09.07.2013 14:39
AzikAtom
Delphi2010:
39. olivenoel , 09.07.2013 16:49
как правильно организовать обмен строками в длл (с учетом СОМ)? Сигнатура метода такая

в метод приходим с именем xls-файла для конвертации. Уходим — с именем полученного после конвертации файла (т.е. фактически изменяется только расширение, потому длина строки не меняется).

сдается мне подобные методы первые кандидаты на утечки.

40. AzikAtom , 09.07.2013 17:00
Если я правильно понимаю концепцию, то в библиотеке память строки не освобождается, и передаётся в неё только указатель, значит, просто там же поменять символы и при выходе из процедуры строка будет изменённая.

цитата: CyberStorm:
Утечка в Delphi может быть если используется string в структуре record.

например утечка будет:
TMyRecord=record
S:string;
End;

утечки не будет:
TMyRecord=record
S:shortstring; // или S:string[100]
End;

так же посмотрела исходники FastMM — если тип утечки не ссылочный (т.е. класс или строка или юникодстрока), то она сообщается как Unknown.

41. olivenoel , 09.07.2013 19:07
42. CyberStorm , 09.07.2013 23:47
Я что-то не понял вашего термина «не зачищаются»
Перечислимые типы в Delphi по умолчанию имеют размер byte т.е. статический размер и утечек из-за них не может быть, при уничтожении класса память, выделенная под переменные при создании класса, высвобождается автоматически.
В конце Destroy я обычно всегда добавляю inherited — правило хорошего тона

цитата: CyberStorm:
Я что-то не понял вашего термина «не зачищаются»
Перечислимые типы в Delphi по умолчанию имеют размер byte т.е. статический размер и утечек из-за них не может быть, при уничтожении класса память, выделенная под переменные при создании класса, высвобождается автоматически.
В конце Destroy я обычно всегда добавляю inherited — правило хорошего тона

если остаются строки, то остается и енум-поле. Сообщение от FastMM гласит: UnicodeString x2 Unknown x1. Или кратно, если объектов было несколько.

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

Добавление от 10.07.2013 10:46:

будет ли утечка в случае

Добавление от 10.07.2013 10:47:

и изменится ли что-то, если стрки сделать короткими?

43. olivenoel , 10.07.2013 10:09
44. CyberStorm , 10.07.2013 10:58
Два раза вызов конструктора?

Инициализацию переменных надо вынести в отдельный метод класса, например Init и вызывать его из конструктора

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

цитата: CyberStorm:
Два раза вызов конструктора?

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

цитата: CyberStorm:
Инициализацию переменных надо вынести в отдельный метод класса, например Init и вызывать его из конструктора

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

Добавление от 10.07.2013 11:24:

как грамотно делать этот Init «сквозным» во всех классах иерархии? В базовом сделать его виртуальным, а потом переопределять во всех наследниках, вызывая при этом родительский? Он нигде не потеряется?

45. olivenoel , 10.07.2013 11:11
46. Spirit-1 , 10.07.2013 11:40
olivenoel
как грамотно делать этот Init «сквозным» во всех классах иерархии? В базовом сделать его виртуальным, а потом переопределять во всех наследниках, вызывая при этом родительский? Он нигде не потеряется?
В родительском классе virtual. В дочерних, при необходимости перекрытия метода, override. Нигде не потеряется.

цитата: CyberStorm:
Утечка в Delphi может быть если используется string в структуре record.

например утечка будет:
TMyRecord=record
S:string;
End;

утечки не будет:
TMyRecord=record
S:shortstring; // или S:string[100]
End;

если применить все это к моей Персоне:

создается три экземпляра Персоны. Утечки: UnicodeString x2 TPerson x3 UnicodeString x4

меняю длинную строку на короткую в

создаю опять три экземпляра. Получаю «чистые» утечки TPerson x3. Куда деваются строки, а точнее откуда они повляются в случае длинных строк?

47. olivenoel , 10.07.2013 12:09
48. Spirit-1 , 10.07.2013 14:03
Создание UnicodeString есть вызов GetMem (см. _NewUnicodeString в system.pas).
Ссылки на созданные строки хранятся в некоторой таблице, формат которой в D2010 примерно такой:

У меня есть подозрение, что в некоторый момент срабатывает что-то вроде GC, который проходит по этой таблице и освобождает память из-под тех строк, у которых RefCount == 0. В тестовом примере вполне может случиться так, что «строковой GC» не успевает сработать, а FastMM рапортует о неосвобождённой памяти. Для чистоты эксперимента я бы насоздавал несколько сотен тысяч объектов с большими/длинными строками и сравнил отчеты об утечках на выходе из программы с теоретически рассчитанными. Если «строковой GC» успел сработать, то количество UnicodeString утечек будет отличаться от общего количества созданных строк.

P.S. Тяжело разобраться, когда полных исходников внутреннуй кухни нет. Остаётся только гадать .

49. CyberStorm , 10.07.2013 17:00
olivenoel Взял ваш код, скомпилировал на XE2, запустил — утечек памяти нет.

Даже убрал из Destroy присвоение:
FName.FFName := »;
FName.FLName := »;
утечек все равно нет, компилятор Delphi достаточно интеллектуальный, чтобы освободить из памяти строки в record при уничтожении экземпляра класса

Давайте посмотрим как вы создаете 3 экземпляра класса вариантов других не вижу. И какая версия Delphi у вас?

PS. Это не .NET, освобождение памяти строки происходит сразу при выходи из области видимости переменной или при вызове деструктора для строк определенных в заголовке класса.

цитата: CyberStorm:
olivenoel Взял ваш код, скомпилировал на XE2, запустил — утечек памяти нет.

Даже убрал из Destroy присвоение:
FName.FFName := »;
FName.FLName := »;
утечек все равно нет, компилятор Delphi достаточно интеллектуальный, чтобы освободить из памяти строки в record при уничтожении экземпляра класса

Давайте посмотрим как вы создаете 3 экземпляра класса вариантов других не вижу. И какая версия Delphi у вас?

у меня тоже ХЕ2. Оно заработало без утечек сегодня после 3-ей или 4-ой перекомпиляции. То ли он компайлер старые дцу-шки подхватывал, то ли звезды не сошлись.

50. olivenoel , 10.07.2013 19:32
51. olivenoel , 11.07.2013 16:50
нашла езе одно место, где появляется Unknown утечка (а точнее строка опять же)

Добавление от 11.07.2013 16:58:

и еще, правильно ли я понимаю, что любое присваивание чего либо переменной типа WideString, автоматично вызывает ф-ции аллокации памяти в системном менеджере памяти? Т.е. после ф-ции

тоже надо вызывать StrDispose/SysFreeString?

52. AzikAtom , 11.07.2013 17:33
olivenoel
можно ли побороть утечку, вызывая StrDispose на стороне хост-приложения?
Да. А можно в функцию передавать готовый буфер для заполнения? В winapi так и делают. Тогда контроль за памятью принадлежит вызывающей стороне.

WideString
Так ведь, WideString это как string только для 16 битных символов.

53. Spirit-1 , 11.07.2013 17:38
olivenoel
Вызов Result := »; при типе функции PWideChar выглядит несколько подозрительно.

цитата: AzikAtom:
olivenoel
можно ли побороть утечку, вызывая StrDispose на стороне хост-приложения?
Да. А можно в функцию передавать готовый буфер для заполнения? В winapi так и делают. Тогда контроль за памятью принадлежит вызывающей стороне.

WideString
Так ведь, WideString это как string только для 16 битных символов.

так ведь WideString полностью совместима с BStr, которая управляется олешным (т.е. общим для всех приложений, системным так сказать) менеджером памяти.

По поводу буффера. Где-то читала, что компилятор дельфи передает Result в виде out параметра. Что-то вроде

эквивалентно (точнее преобразуется не то парсером кода, не то самим компайлером)

Добавление от 11.07.2013 17:56:

цитата: Spirit-1:
olivenoel
Вызов Result := »; при типе функции PWideChar выглядит несколько подозрительно.

чем? Надо что ли nil возвращать?

54. olivenoel , 11.07.2013 17:56
55. Spirit-1 , 11.07.2013 18:15
Я бы Nil возвращал. Все-таки результат есть указатель, и это было бы логичнее.

Добавление от 11.07.2013 18:17:

olivenoel
И тогда все пользователи этой длл должны будут сильно вдаваться в подробности реализации
Кстати, правильно ли я понимаю, что память выделяется в dll, а освобождаться должна вызывающей стороной?

56. olivenoel , 11.07.2013 18:22
правильно Кто-то же ее должен освобождать. Или?

может все же дешевле переименовать все PWideChar в BStr и не морочить разные места?

Добавление от 11.07.2013 18:32:

то что получится на стороне хоста? WideString будет существовать только в scope как временная переменная. При выходе и scope она уничтожится (освободится ли. ). Т.е. Result по выходе из scope будет показывать в небо?

57. Spirit-1 , 11.07.2013 18:44
olivenoel
правильно Кто-то же ее должен освобождать. Или?
Реально это сильно неправильно. Аллоцирование памяти внутри осуществляется средствами конкретного memory manager’а в пуле памяти самой DLL. Освободить такое может только программа с аналогичным MM, и то она будет рыться в чужом огороде. Попробуйте скомпилировать вызывающую программу из другой версии Delphi (чтобы она использовала другой memory manager). И удивитесь, поскольку нифига работать не будет. Или другой вариант — вызывающая программа вообще, к примеру, на MS VC. Как будете память освобождать? Какими вызовами? (За доп. информацией можно поискать, для чего раньше в дельфях использовали borlandmm.dll)
В Вашем случае правильно делать именно так, как посоветовал AzikAtom. Вызывающая программа сама выделяет буфет. Функции DLL заполняют его (буфер) данными. Освобождение памяти делает тоже вызывающая программа. Вот это будет кошерно.
58. AzikAtom , 11.07.2013 18:58
olivenoel
При выходе и scope она уничтожится (освободится ли. )
Да.

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

цитата: Spirit-1:
olivenoel
правильно Кто-то же ее должен освобождать. Или?
Реально это сильно неправильно. Аллоцирование памяти внутри осуществляется средствами конкретного memory manager’а в пуле памяти самой DLL. Освободить такое может только программа с аналогичным MM, и то она будет рыться в чужом огороде.

цитата: Spirit-1:
olivenoel
Попробуйте скомпилировать вызывающую программу из другой версии Delphi (чтобы она использовала другой memory manager). И удивитесь, поскольку нифига работать не будет. Или другой вариант — вызывающая программа вообще, к примеру, на MS VC. Как будете память освобождать? Какими вызовами? (За доп. информацией можно поискать, для чего раньше в дельфях использовали borlandmm.dll)
В Вашем случае правильно делать именно так, как посоветовал AzikAtom. Вызывающая программа сама выделяет буфет. Функции DLL заполняют его (буфер) данными. Освобождение памяти делает тоже вызывающая программа. Вот это будет кошерно.

это ВинАпи стиль. Есть еще СОМ стиль. И там все «играют в одной песочнице» вызовами SysAllocString/SysFreeString/Sys. . Для этого надо все вызовы строковые заставить работать с BStr. Или?

59. olivenoel , 11.07.2013 19:03
60. AzikAtom , 11.07.2013 20:14
olivenoel
ну так WideString и аллоцируется OS’ью в системном ММ
Так вы возвращаете не WideString, а PWideString — указатель на строку. Как вы его дальше используете?
61. Spirit-1 , 11.07.2013 21:52
Я вижу в Вашем коде StrAlloc. Он вызывает GetMem, который выделяет память из СВОЕЙ кучи.

цитата: Spirit-1:
Я вижу в Вашем коде StrAlloc. Он вызывает GetMem, который выделяет память из СВОЕЙ кучи.

Добавление от 12.07.2013 10:31:

цитата: AzikAtom:
olivenoel
ну так WideString и аллоцируется OS’ью в системном ММ
Так вы возвращаете не WideString, а PWideString — указатель на строку. Как вы его дальше используете?

Добавление от 12.07.2013 11:37:

Такой код компилируется и даже, за редким исключением, даёт ожидаемый результат. Но тем не менее, в этом коде грубая ошибка. Указатель, возвращаемый функцией, указывает на область памяти, которая считается свободной — после того как переменная S вышла за пределы области видимости, память, которую занимала эта строка, освободилась. Менеджер памяти может в любой момент вернуть эту память системе (тогда обращение к ней вызовет Access violation) или задействовать для других целей (тогда новая информация перетрёт содержащуюся там строку). Проблема маскируется тем, что обычно результат используется немедленно, до того как менеджер памяти что-то сделает с этим блоком. Тем не менее, полагаться на это и писать такой код не стоит. Под использованием PChar в комментарии имеется ввиду использование его таким образом, как он используется в API-функциях: программа выделяет память для буфера, указатель на этот буфер передаёт в DLL как PChar, а DLL только заносит в этот буфер требуемое значение.

с областями видимости возвращаемых переменных у меня все намного проще — они глобальные переменные в модуле library — т.е. существуют пока загружена длл. Многие ф-ции, возвращающие строки были описаны как:

и все работало (не знаю правда сколько процентов «повезло» в этом). Если поменять это на

3.3.1. Виды строк в Delphi

3.3.1. Виды строк в Delphi

Для работы с кодировкой ANSI в Delphi существует три вида строк: AnsiString , ShortString и PChar . Различие между ними заключается в способе хранения строки, а также выделения и освобождения памяти для нее. Зарезервированное слово string по умолчанию означает тип AnsiString , но если после нее следует число в квадратных скобках, то это означает тип ShortString , а число — ограничение по длине. Кроме того, существует опция компилятора Huge strings (управляется также директивами компилятора <$H+/->и <$LONGSTRINGS ON/OFF>, которая по умолчанию включена, но если ее выключить, то слово string станет эквивалентно ShortString ; или, что то же самое, string[255] . Эта опция введена для обратной совместимости с Turbo Pascal, в новых программах отключать ее нет нужды. Внутреннее устройство этих типов данных иллюстрирует рис. 3.2.

Рис. 3.2. Устройство различных строковых типов Delphi

Наиболее просто устроен тип ShortString . Это массив символов с индексами от 0 до N, где N — число символов, указанное при объявлении переменной (в случае использования идентификатора ShortString N явно не указывается и равно 255). Нулевой элемент массива хранит текущую длину строки, которая может быть меньше или равна объявленной (эту длину мы будем далее обозначать M), элементы с индексами от 1 до M — это символы, составляющие строку. Значения элементов с индексами M+1..N не определены. Все стандартные функции для работы со строками игнорируют эти символы. В памяти такая переменная всегда занимает N+1 байтов.

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

В системе приняты так называемые нуль-терминированные строки: строка передается указателем на ее первый символ, длина строки отдельно нигде не хранится, признаком конца строки считается встретившийся в цепочке символов #0 . Длина таких строк ограничена только доступной памятью и способом адресации (т. е. в Windows теоретически это 4 294 967 295 символов). Для работы с такими строками предусмотрен тип PChar . Переменная такого типа является указателем на начало строки. В литературе нередко можно встретить утверждение, что PChar = ^Сhar , однако это неверно: тип PChar встроен в компилятор и не выводится из других типов. Это позволяет выполнять с ним операции, недопустимые для других указателей. Во-первых, если P — переменная типа PChar , то допустимо обращение к отдельным символам строки с помощью конструкции P[N] , где N — целочисленное выражение, определяющее номер символа (в отличие от типа ShortString , здесь символы нумеруются с 0, а не с 1). Во-вторых, к указателям типа PChar разрешено добавлять и вычитать целые числа, смещая указатель на соответствующее число байтов вверх или вниз (здесь речь идет только об операторах «+» и «-«; адресная арифметика с помощью процедур Inc и Dec доступна для любых типизированных указателей, а не только для PChar ).

При работе с PChar программист целиком и полностью отвечает за выделение памяти для строки и за ее освобождение. Именно это и служит основным источником ошибок у новичков: они пытаются работать с такими строками так же, как и с AnsiString , надеясь, что операции с памятью будут выполнены автоматически. Это очень грубая ошибка, способная привести к самым непредсказуемым последствиям.

Хотя программист имеет полную свободу выбора в том, как именно выделять и освобождать память для нуль-терминированных строк, в большинстве случаев самыми удобными оказываются специально предназначенные для этого функции StrNew , StrDispose и т. п. Их преимущество заключается в том, что менеджер памяти выделяет чуть больше места, чем требуется для хранения строки, и в эту дополнительную память записывается, сколько байтов было выделено. Благодаря этому функция StrDispose удаляет ровно столько памяти, сколько было выделено, даже если в середину выделенного блока был записан символ #0 , уменьшающий длину строки.

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

Тип AnsiString объединяет достоинства типов ShortString и PChar : строки имеют фактически неограниченную длину, заботиться о выделении памяти для них не нужно, в их конец автоматически добавляется символ #0 , что делает их совместимыми с системными строками (впрочем, эта совместимость не абсолютная; как и когда можно использовать AnsiString в функциях API, мы рассматривали в разд. 1.1.13.).

Переменная типа AnsiString — это указатель на первый символ строки, как и в случае PChar . Разница в том, что перед этой строкой в память записывается дополнительная информация: длина строки и счетчик ссылок. Это позволяет компилятору генерировать код, автоматически выделяющий, перераспределявший и освобождающий память, выделяемую для строки. Работа с памятью происходит совершенно прозрачно для программиста, в большинстве случаев со строками AnsiString можно работать, вообще не задумываясь об их внутреннем устройстве. Символы в таких строках нумеруются с единицы, чтобы облегчить перенос старых программ, использовавших строки типа ShortString .

Счетчик ссылок позволяет реализовать то, что называется copy-on-demand, копирование по необходимости. Если у нас есть две переменные S1 , S2 типа AnsiString , присваивание вида S1:= S2 не приводит к копированию всей строки. Вместо этого в указатель S1 копируется значение указателя S2 , а счетчик ссылок строки увеличивается на единицу. В дальнейшем, если одну из этих строк потребуется модифицировать, она сначала будет скопирована (а счетчик ссылок оригинала, естественно, уменьшен) и только потом изменена, чтобы это не затрагивало остальные переменные.

Далее мы рассмотрим, какие проблемы могут возникнуть при использовании строк разного вида.

AnsiReverseString — Функция Delphi

6197 просмотра

1 ответ

737 Репутация автора

Я пытаюсь изменить строку в Delphi. Как вы можете перевернуть строку, используя функцию AnsiReverseString?

Какую единицу нужно добавить и использовать?

Ответы (1)

4 плюса

77 Репутация автора

Как все говорят в комментариях, добавьте StrUtils модуль к своему uses предложению.

AnsiString Delphi XE7 [TEncoding]

Вопрос

Satellite 13

Satellite 13

  • Продвинутый пользователь
  • Пользователи
  • 13
  • 52 сообщения

На Windows тип AnsiString присутствует, на Android отсутствует.

Имеется код (функция):

На Windows результат отображается нормально, потому что ss:=AnsiString;

А вот на Android’e нету значения AnsiString, поэтому для теста я просто присвоил String; Отсюда пошли кракозябры, как можно получить результат в нормальной читаемой кодировке?

Почитав немного, наткнулся на TEncoding, но не разобрался.

Илон Маск рекомендует:  Что такое код hw_getchildcoll
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL
62. olivenoel , 12.07.2013 10:03