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;