Delphi. Массив байтов в строку шестнадцатеричных значений.

Иногда бывает нужно посмотреть значение массива байтов в понятном человеку виде, чаще всего в виде пар шестнадцатеричных цифр. Задача может быть решена простой функцией, на примере которой мы увидим три подхода к работе со строками в Delphi и Паскале.

Паскалевские строки: наглядно и просто

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

function ByteToStr(bytes: array of byte): string;
const
  BytesHex: array[0..15] of char = 
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
var
  i, len: integer;
begin
  len := Length(bytes);
  SetLength(Result, len * 2);
  for i := 0 to len - 1 do begin
    Result[i * 2 + 1] := BytesHex[bytes[i] shr 4];
    Result[i * 2 + 2] := BytesHex[bytes[i] and $0F];
  end;
end;

Пример использования

const
  Bytes: array[0..11] of byte = (1, 2, 3, 4, 5, 10, 25, 35, 99, 122, 173, 255);
...
Writeln(ByteToStr(Bytes));
...

На экране видим: 01020304050A1923637AADFF

Функцию легко модифицировать, чтобы показывать значения в формате 0xDD или разделенные пробелом.

function ByteToStr(bytes: array of byte): string;
const
  BytesHex: array[0..15] of char = 
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
var
  i, len: integer;
begin
  len := Length(bytes);
  SetLength(Result, len * 5);
  for i := 0 to len - 1 do begin
    Result[i * 5 + 1] := '0';
    Result[i * 5 + 2] := 'x';
    Result[i * 5 + 3] := BytesHex[bytes[i] shr 4];
    Result[i * 5 + 4] := BytesHex[bytes[i] and $0F];
    Result[i * 5 + 5] := ' ';
  end;
end;

Результат: 0x01 0x02 0x03 0x04 0x05 0x0A 0x19 0x23 0x63 0x7A 0xAD 0xFF

"Сишные" строки: менее наглядно, но быстрее

В предыдущем примере мы использовали паскалевские строки, хотя известно, что управление ими менее эффективно, чем, например, прямые манипуляции указателями PChar в сишном стиле. Например, если открыть дизассемблер, то при каждом обращении к строке будет вызываться функция UniqueString. Это добавит несколько тактов процессора на каждое такое присвоение.

Зато первый пример был наглядный. Не зря же Вирт придумал язык! Поэтому приведем и более оптимальный по скорости, чтобы можно было сравнить.

function ByteToStr(bytes: array of byte): string;
const
  BytesHex: array[0..15] of char = 
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
var
  i, len: integer;
  s: PChar;
begin
  len := Length(bytes);
  SetLength(Result, len * 5);
  s := StrAlloc(len * 5 + 1);
  for i := 0 to len - 1 do begin
    StrLCopy(@s[i * 5], '0x', 2);
    StrLCopy(@s[i * 5 + 2], @BytesHex[bytes[i] shr 4], 1);
    StrLCopy(@s[i * 5 + 3], @BytesHex[bytes[i] and $0F], 1);
    StrLCopy(@s[i * 5 + 4], ' ', 1);
  end;
  Result := s;
  SysUtils.StrDispose(s);
end;

Прямое управление указателем в ассемблерном стиле: максимальная эффективность

function ByteToStr(bytes: array of byte): string;
const
  BytesHex: array[0..15] of char = 
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
var
  i, j, len: integer;
  s: PChar;
begin
  len := Length(bytes);
  SetLength(Result, len * 5);
  s := StrAlloc(len * 5 + 1);
  j := 0;
  for i := 0 to len - 1 do begin
    s[j] := '0';
    Inc(j);
    s[j] := 'x';
    Inc(j);
    s[j] := BytesHex[bytes[i] shr 4];
    Inc(j);
    s[j] := BytesHex[bytes[i] and $0F];
    Inc(j);
    s[j] := ' ';
    Inc(j);
  end;
  Result := s;
  SysUtils.StrDispose(s);
end;

Комментарии

Изображение пользователя Alexey_Donskoy.

Ага...

Ну что ж, для некоторых читателей это может быть полезным открытием ;)
Хотя способ с незапамятных времён широко применяется при программировании на ассемблере...
А в ряде компиляторов того же Паскаля аналогично реализован CASE (не зря же физический размер селектора ограничен одним байтом)...

Изображение пользователя st.

Добавил

Добавил замечание по поводу паскалевских строк. Разумеется прямые манипуляции указателями на char будут работать быстрее.

Delphi. Массив байтов

Очень полезная штука. Иногда ищешь и не можешь найти, а иногда очень нужно. Отличная статья, хорошо оформлен код.