2.1. Преобразование структуры данных в массив байт
2.1.1. Разбор задачи
В индивидуальных вариантах задач на тему преобразования структуры данных в массив байт предлагается написать программу на языке C или C++ для работы со структурой данных c учетом архитектурных особенностей некоторой платформы. Рассмотрим платформу, информация о которой представлена в табл. 3, с порядком байт от старшего к младшему.
|
Тип |
Размер (байт) |
Знак |
Выравнивание (байт) |
|---|---|---|---|
|
short |
4 |
Да |
1 |
|
char |
1 |
Нет |
1 |
|
float |
4 |
Да |
8 |
|
unsigned int |
8 |
Нет |
1 |
Структура данных на целевой платформе имеет следующий вид, типы в приведенной структуре данных следует адаптировать к используемому в решении задачи компилятору:
struct data {
short field1;
char field2;
short field3;
char field4[9];
float field5;
float field6;
unsigned int field7;
};
Необходимо написать функцию для преобразования структуры data в массив байт. В реализации необходимо учесть особенности платформы и вывести на экран значения массива байт, как показано в примерах ниже.
Пример 1
Функция для заполнения полей структуры data:
void test_code1(struct data *d) {
d->field1 = 577412691;
d->field2 = 50;
d->field3 = -1430727895;
d->field4[0] = 223;
d->field4[1] = 172;
d->field4[2] = 195;
d->field4[3] = 169;
d->field4[4] = 31;
d->field4[5] = 107;
d->field4[6] = 61;
d->field4[7] = 233;
d->field4[8] = 167;
d->field5 = -0.36180755496025085;
d->field6 = -0.06448640674352646;
d->field7 = 1362959830220383687;
}
Результат вывода массива байт на экран:
22 6A 9E 53 32 AA B8 D3
29 DF AC C3 A9 1F 6B 3D
E9 A7 00 00 00 00 00 00
BE B9 3E D7 00 00 00 00
BD 84 11 73 12 EA 34 BE
8B 69 A5 C7
Пример 2
Функция для заполнения полей структуры data:
void test_code2(struct data *d) {
d->field1 = 1539482488;
d->field2 = 71;
d->field3 = 499575056;
d->field4[0] = 225;
d->field4[1] = 148;
d->field4[2] = 133;
d->field4[3] = 246;
d->field4[4] = 83;
d->field4[5] = 176;
d->field4[6] = 211;
d->field4[7] = 200;
d->field4[8] = 226;
d->field5 = 0.35434696078300476;
d->field6 = -0.5025861859321594;
d->field7 = 13314929168479759564;
}
Результат вывода массива байт на экран:
5B C2 A3 78 47 1D C6 E9
10 E1 94 85 F6 53 B0 D3
C8 E2 00 00 00 00 00 00
3E B5 6C F7 00 00 00 00
BF 00 A9 7D B8 C8 21 88
80 B9 2C CC
Решение задачи
Как указано в постановке задачи, при преобразовании структуры данных в массив байт необходимо учесть архитектурные особенности целевой платформы, при этом целевая платформа может отличаться от той платформы, на которой ведётся разработка программы – далее в этом разделе будем считать, что отладка и тестирование программы производится на устройстве с архитектурой x86-64 под управлением ОС Windows.
Начать решение задачи следует с адаптации типов полей структуры, перечисленных в табл. 3, к компилятору, используемому в процессе разработки.
Из табл. 3 следует, что на целевой платформе тип short является знаковым и занимает 4 байта, однако, на устройстве с ОС Windows x86-64 тип short занимает 2 байта. Для учёта этой особенности целевой платформы в нашем коде заменим поля структуры типа short на поля типа int32_t, занимающие 4 байта памяти на любой платформе.
Как указано в табл. 3, тип char на целевой платформе является беззнаковым – для учёта этой особенности при разработке на ОС Windows x86-64 необходимо заменить поля структуры типа char на поля типа uint8_t.
Кроме того, в табл. 3 указано, что тип unsigned int на целевой платформе занимает 8 байт, однако, на устройстве с ОС Windows x86-64 переменные типа unsigned int занимают 4 байта – в этой связи необходимо заменить в определении структуры тип unsigned int на тип uint64_t, занимающий 8 байт на любой платформе.
Определения типов int32_t, uint8_t, uint64_t приведены в стандартном заголовочном файле stdint.h, который необходимо подключить перед определением структуры. Характеристики типа float, приведённые в табл. 3, совпадают с характеристиками этого типа данных на платформе ОС Windows x86-64, поэтому поля типа float в структуре data оставим без изменений.
Применив указанные преобразования к структуре из постановки задачи, получим следующее определение структуры data:
#include <stdint.h>
#include <stdio.h>
struct data {
int32_t field1;
uint8_t field2;
int32_t field3;
uint8_t field4[9];
float field5;
float field6;
uint64_t field7;
};
Как показано в примерах результатов вычислений, массив байт, соответствующий структуре data, следует выводить на экран построчно. Каждая выведенная строка содержит до 8 байт, разделённых пробелами, при этом байты необходимо вывести в виде шестнадцатеричной строки в верхнем регистре.
Для учёта указанных особенностей выходного формата реализуем вспомогательную функцию move и функцию print_uint8_t. Проверим работу реализованных функций – для этого в функции main объявим структуру data и заполним её тестовыми значениями при помощи приведённой в постановке задачи функции test_code1, после чего выведем на экран байты, соответствующие полю field2 и первому элементу массива в поле field4:
int move(int offs) {
offs += 1;
if (offs % 8 == 0)
printf("\n");
return offs;
}
int print_uint8_t(uint8_t byte, int offs) {
printf("%02X ", byte);
return move(offs);
}
int main() {
struct data d;
test_code1(&d);
int offs = 0;
offs = print_uint8_t(d.field2, offs);
offs = print_uint8_t(d.field4[0], offs);
return 0;
}
Функция move принимает на вход номер текущего байта, вычисляет номер следующего байта и печатает на экран символ переноса строки \n в том случае, если номер следующего байта кратен 8. Функция print_uint8_t выводит на экран значение поданного на вход байта в виде шестнадцатеричной строки в верхнем регистре за счёт использования формата X. Последовательность 02, указанная между символом % и форматом X, позволяет установить минимальную длину выводимой строки равной 2, при этом символ 0 будет добавлен в начало выводимой строки в случае, если её длина меньше минимальной.
После печати значения байта в виде шестнадцатеричной строки функцией print_uint8_t за счёт использования функции move на экран также будет выведен символ переноса строки на новую \n в том случае, если на экран уже было выведено 8 байт.
Поместим в файл exam.c исправленное определение структуры data, приведённую в постановке задачи функцию test_code1, а также функции move, print_uint8_t и main, скомпилируем программу при помощи компилятора gcc из набора инструментов MinGW [5] и проверим её работу:
PS C:\> gcc -std=c99 -Wall -Wextra -Wpedantic exam.c -o exam.exe
PS C:\> ./exam.exe
32 DF
Опции компилятора, начинающиеся с префикса -W, позволяют включить дополнительные предупреждения для обнаружения ошибок и несоответствий стандарту при компиляции кода на языке C.
Первое выведенное на экран значение – это байт, соответствующий полю field2, его значение совпадает со значением 5-го байта из 1-го примера (см. результат вывода массива байт на экран для функции test_code1), поскольку полю field2 предшествуют 4 байта поля field1. Второе выведенное на экран значение – это байт 1-го элемента массива field4, его значение совпадает со значением 10-го байта из 1-го примера, так как ему предшествуют поля field1 и field3, занимающие по 4 байта, а также поле field2, занимающее 1 байт.
На следующем шаге реализуем функции print_int32_t и print_uint64_t для вывода на экран байт целочисленных полей структуры data размером 4 и 8 байт соответственно.
Как указано в постановке задачи, порядок байт на целевой платформе – от старшего к младшему. Порядок байт целевой платформы отличается от порядка байт на устройстве с архитектурой x86-64. На x86-64 используется порядок байт от младшего к старшему, и в связи с этим при выводе байт на экран необходимо менять их порядок – для этого в функциях print_int32_t и print_uint64_t будем выводить байты на экран в цикле for, перебирающем значения по адресу bytes в обратном порядке.
Проверим работу реализованных функций – для этой цели в функции main выведем на экран значения первых 4-х полей структуры data:
int print_int32_t(int32_t field, int offs) {
uint8_t *bytes = (uint8_t*) &field;
for (int i = 4; i > 0; i -= 1)
offs = print_uint8_t(bytes[i-1], offs);
return offs;
}
int print_uint64_t(uint64_t field, int offs) {
uint8_t *bytes = (uint8_t*) &field;
for (int i = 8; i > 0; i -= 1)
offs = print_uint8_t(bytes[i-1], offs);
return offs;
}
int main() {
struct data d;
test_code1(&d);
int offs = 0;
offs = print_int32_t(d.field1, offs);
offs = print_uint8_t(d.field2, offs);
offs = print_int32_t(d.field3, offs);
for (int i = 0; i < 9; i++)
offs = print_uint8_t(d.field4[i], offs);
return 0;
}
Каждая из функций print_int32_t и print_uint64_t принимает на вход содержимое поля field, при помощи оператора & извлекает адрес поля field, после чего при помощи оператора приведения типов (uint8_t*) меняет тип указателя на uint8_t* – на указатель на первый байт. Этот указатель затем используется для вывода на экран значения каждого i-го байта при помощи функции print_uint8_t, выражение bytes[i-1] эквивалентно выражению *(bytes+i-1) и позволяет получить значение байта с номером i-1 начиная с адреса указателя bytes.
Проверим работу обновлённой программы:
PS C:\> gcc -std=c99 -Wall -Wextra -Wpedantic exam.c -o exam.exe
PS C:\> ./exam.exe
22 6A 9E 53 32 AA B8 D3
29 DF AC C3 A9 1F 6B 3D
E9 A7
Значения байт, выведенные в консоль, совпадают со значениями байт, приведёнными в постановке задачи (см. результат вывода массива байт на экран для функции test_code1). Первая строка, содержащая 8 байт, была автоматически отделена от следующей строки за счёт использования функции move, печатающей символ \n после каждого 8-го байта.
Как указано в табл. 3, поля типа float на целевой платформе должны быть выровнены по 8 байт – на практике это означает, что адреса первых байт полей типа float должны быть кратны 8. Реализуем функцию print_align_8 для печати данных, выровненных по 8 байт. Реализуем также функцию print_float и обновим функцию main для вывода на экран байт всех полей структуры data:
int print_align_8(int offs) {
while (offs % 8 != 0)
offs = print_uint8_t(0, offs);
return offs;
}
int print_float(float field, int offs) {
uint8_t *bytes = (uint8_t*) &field;
for (int i = 4; i > 0; i -= 1)
offs = print_uint8_t(bytes[i - 1], offs);
return offs;
}
int main() {
struct data d;
test_code1(&d);
int offs = 0;
offs = print_int32_t(d.field1, offs);
offs = print_uint8_t(d.field2, offs);
offs = print_int32_t(d.field3, offs);
for (int i = 0; i < 9; i++)
offs = print_uint8_t(d.field4[i], offs);
offs = print_align_8(offs);
offs = print_float(d.field5, offs);
offs = print_align_8(offs);
offs = print_float(d.field6, offs);
offs = print_uint64_t(d.field7, offs);
return 0;
}
Функция print_align_8 увеличивает номер выводимого на экран байта и печатает 00 до тех пор, пока не встретится номер байта, который делится на 8 без остатка, после чего завершает работу. В этой связи вызывать функцию print_align_8 необходимо перед вызовом функции print_float для выравнивания полей типа float.
Проверим работу программы:
PS C:\> gcc -std=c99 -Wall -Wextra -Wpedantic exam.c -o exam.exe
PS C:\> ./exam.exe
22 6A 9E 53 32 AA B8 D3
29 DF AC C3 A9 1F 6B 3D
E9 A7 00 00 00 00 00 00
BE B9 3E D7 00 00 00 00
BD 84 11 73 12 EA 34 BE
8B 69 A5 C7
Для проверки корректности работы программы на 2 примерах входных данных, представленных функциями test_code1 и test_code2, переместим код для вывода на экран структуры в виде массива байт в функцию print_struct:
void print_struct(struct data *d) {
int offs = 0;
offs = print_int32_t(d->field1, offs);
offs = print_uint8_t(d->field2, offs);
offs = print_int32_t(d->field3, offs);
for (int i = 0; i < 9; i++)
offs = print_uint8_t(d->field4[i], offs);
offs = print_align_8(offs);
offs = print_float(d->field5, offs);
offs = print_align_8(offs);
offs = print_float(d->field6, offs);
offs = print_uint64_t(d->field7, offs);
}
int main() {
struct data d;
test_code1(&d);
print_struct(&d);
printf("\n\n");
test_code2(&d);
print_struct(&d);
return 0;
}
Для доступа к значениям полей структуры data в функции print_struct используется оператор -> вместо оператора точки, поскольку на вход функции print_struct передаётся указатель на структуру struct data *d – синтаксис d->field эквивалентен синтаксису (*d).field, где выражение *d получает объект по его адресу, сохранённому в переменной d.
Проверим работу программы на тестовых данных:
PS C:\> gcc -std=c99 -Wall -Wextra -Wpedantic exam.c -o exam.exe
PS C:\> ./exam.exe
22 6A 9E 53 32 AA B8 D3
29 DF AC C3 A9 1F 6B 3D
E9 A7 00 00 00 00 00 00
BE B9 3E D7 00 00 00 00
BD 84 11 73 12 EA 34 BE
8B 69 A5 C7
5B C2 A3 78 47 1D C6 E9
10 E1 94 85 F6 53 B0 D3
C8 E2 00 00 00 00 00 00
3E B5 6C F7 00 00 00 00
BF 00 A9 7D B8 C8 21 88
80 B9 2C CC
2.1.2. Упражнения
Задача 1
Написать программу на C/C++ для работы со структурой данных c учетом архитектурных особенностей некоторой платформы. Информация о платформе представлена в табл. 4, порядок байт – от старшего к младшему.
|
Тип |
Размер (байт) |
Знак |
Выравнивание (байт) |
|---|---|---|---|
|
unsigned short |
2 |
Нет |
8 |
|
char |
1 |
Нет |
1 |
|
unsigned int |
4 |
Нет |
1 |
|
long long |
8 |
Да |
4 |
Структура данных на целевой платформе имеет следующий вид, типы в приведенной структуре данных, возможно, придется адаптировать к используемому в решении задачи компилятору:
struct data {
unsigned short field1;
char field2[3];
unsigned int field3;
long long field4;
};
Написать функцию для преобразования структуры data в массив байт. В реализации необходимо учесть особенности платформы. Вывести на экран значения массива байт, как показано в примерах ниже.
Пример 1
Функция для заполнения полей структуры data:
void test_code1(struct data *d) {
d->field1 = 6122;
d->field2[0] = 110;
d->field2[1] = 178;
d->field2[2] = 32;
d->field3 = 1897468891;
d->field4 = -7251212081377159638;
}
Результат вывода массива байт на экран:
17 EA 6E B2 20 71 19 13
DB 00 00 00 9B 5E 85 07
D2 7E 56 2A
Пример 2
Функция для заполнения полей структуры data:
void test_code2(struct data *d) {
d->field1 = 40929;
d->field2[0] = 98;
d->field2[1] = 78;
d->field2[2] = 104;
d->field3 = 322651602;
d->field4 = 6308753995488277642;
}
Результат вывода массива байт на экран:
9F E1 62 4E 68 13 3B 45
D2 00 00 00 57 8D 32 55
6C 67 B4 8A
Задача 2
Написать программу на C/C++ для работы со структурой данных c учетом архитектурных особенностей некоторой платформы. Информация о платформе представлена в табл. 5, порядок байт – от младшего к старшему.
|
Тип |
Размер (байт) |
Знак |
Выравнивание (байт) |
|---|---|---|---|
|
char |
1 |
Да |
1 |
|
unsigned short |
2 |
Нет |
1 |
|
long long |
8 |
Да |
8 |
Структура данных на целевой платформе имеет следующий вид, типы в приведенной структуре данных, возможно, придется адаптировать к используемому в решении задачи компилятору:
struct data {
char field1[5];
unsigned short field2;
long long field3;
char field4;
unsigned short field5;
unsigned short field6;
};
Написать функцию для преобразования структуры data в массив байт. В реализации необходимо учесть особенности платформы. Вывести на экран значения массива байт, как показано в примерах ниже.
Пример 1
Функция для заполнения полей структуры data:
void test_code1(struct data *d) {
d->field1[0] = 89;
d->field1[1] = 122;
d->field1[2] = 3;
d->field1[3] = -123;
d->field1[4] = 109;
d->field2 = 65240;
d->field3 = 7002812016868415575;
d->field4 = -122;
d->field5 = 14233;
d->field6 = 33981;
}
Результат вывода массива байт на экран:
59 7A 03 85 6D D8 FE 00
57 58 47 46 6D FC 2E 61
86 99 37 BD 84
Пример 2
Функция для заполнения полей структуры data:
void test_code2(struct data *d) {
d->field1[0] = 20;
d->field1[1] = 21;
d->field1[2] = 119;
d->field1[3] = 17;
d->field1[4] = -109;
d->field2 = 6137;
d->field3 = -1989174332666178083;
d->field4 = -125;
d->field5 = 13528;
d->field6 = 29366;
}
Результат вывода массива байт на экран:
14 15 77 11 93 F9 17 00
DD 95 1B C9 7B 08 65 E4
83 D8 34 B6 72
Задача 3
Написать программу на C/C++ для работы со структурой данных c учетом архитектурных особенностей некоторой платформы. Информация о платформе представлена в табл. 6, порядок байт – от старшего к младшему.
|
Тип |
Размер (байт) |
Знак |
Выравнивание (байт) |
|---|---|---|---|
|
float |
4 |
Да |
2 |
|
char |
1 |
Да |
1 |
|
double |
8 |
Да |
2 |
|
unsigned long |
4 |
Нет |
8 |
Структура данных на целевой платформе имеет следующий вид, типы в приведенной структуре данных, возможно, придется адаптировать к используемому в решении задачи компилятору:
struct data {
float field1;
char field2[7];
double field3;
unsigned long field4[5];
float field5;
};
Написать функцию для преобразования структуры data в массив байт. В реализации необходимо учесть особенности платформы. Вывести на экран значения массива байт, как показано в примерах ниже.
Пример 1
Функция для заполнения полей структуры data:
void test_code1(struct data *d) {
d->field1 = -0.31428834795951843;
d->field2[0] = 48;
d->field2[1] = 123;
d->field2[2] = -46;
d->field2[3] = 18;
d->field2[4] = -108;
d->field2[5] = -74;
d->field2[6] = -94;
d->field3 = -0.11718039696756088;
d->field4[0] = 3439945603;
d->field4[1] = 3356052132;
d->field4[2] = 1245866636;
d->field4[3] = 688797180;
d->field4[4] = 246177702;
d->field5 = 0.5174322724342346;
}
Результат вывода массива байт на экран:
BE A0 EA 67 30 7B D2 12
94 B6 A2 00 BF BD FF 88
D4 B5 3D A0 00 00 00 00
CD 09 67 83 00 00 00 00
C8 09 4A A4 00 00 00 00
4A 42 6A 8C 00 00 00 00
29 0E 35 FC 00 00 00 00
0E AC 5F A6 3F 04 76 71
Пример 2
Функция для заполнения полей структуры data:
void test_code2(struct data *d) {
d->field1 = -0.8661420941352844;
d->field2[0] = -7;
d->field2[1] = 50;
d->field2[2] = -82;
d->field2[3] = -60;
d->field2[4] = 123;
d->field2[5] = -59;
d->field2[6] = 24;
d->field3 = 0.4712565849740169;
d->field4[0] = 103666961;
d->field4[1] = 1135678811;
d->field4[2] = 784585022;
d->field4[3] = 867071605;
d->field4[4] = 2384339639;
d->field5 = -0.17117364704608917;
}
Результат вывода массива байт на экран:
BF 5D BB 7D F9 32 AE C4
7B C5 18 00 3F DE 29 11
61 1F 3C 2C 00 00 00 00
06 2D D5 11 00 00 00 00
43 B1 15 5B 00 00 00 00
2E C3 D1 3E 00 00 00 00
33 AE 76 75 00 00 00 00
8E 1E 22 B7 BE 2F 48 25
Задача 4
Написать программу на C/C++ для работы со структурой данных c учетом архитектурных особенностей некоторой платформы. Информация о платформе представлена в табл. 7, порядок байт – от младшего к старшему.
|
Тип |
Размер (байт) |
Знак |
Выравнивание (байт) |
|---|---|---|---|
|
int |
4 |
Да |
8 |
|
unsigned int |
4 |
Нет |
8 |
|
char |
1 |
Нет |
1 |
|
unsigned short |
4 |
Нет |
1 |
Структура данных на целевой платформе имеет следующий вид, типы в приведенной структуре данных, возможно, придется адаптировать к используемому в решении задачи компилятору:
struct data {
int field1;
unsigned int field2;
char field3[4];
unsigned int field4;
unsigned short field5[8];
int field6;
};
Написать функцию для преобразования структуры data в массив байт. В реализации необходимо учесть особенности платформы. Вывести на экран значения массива байт, как показано в примерах ниже.
Пример 1
Функция для заполнения полей структуры data:
void test_code1(struct data *d) {
d->field1 = -1350465593;
d->field2 = 1070862709;
d->field3[0] = 133;
d->field3[1] = 250;
d->field3[2] = 100;
d->field3[3] = 119;
d->field4 = 853061266;
d->field5[0] = 4045919985;
d->field5[1] = 621142676;
d->field5[2] = 2522688801;
d->field5[3] = 4266396485;
d->field5[4] = 4011826561;
d->field5[5] = 2781046377;
d->field5[6] = 4181524247;
d->field5[7] = 2634240086;
d->field6 = 1526700227;
}
Результат вывода массива байт на экран:
C7 87 81 AF 00 00 00 00
75 11 D4 3F 85 FA 64 77
92 AE D8 32 F1 D6 27 F1
94 E2 05 25 21 2D 5D 96
45 0B 4C FE 81 9D 1F EF
69 66 C3 A5 17 FF 3C F9
56 50 03 9D 00 00 00 00
C3 98 FF 5A
Пример 2
Функция для заполнения полей структуры data:
void test_code2(struct data *d) {
d->field1 = 1629770468;
d->field2 = 2964430478;
d->field3[0] = 80;
d->field3[1] = 4;
d->field3[2] = 142;
d->field3[3] = 93;
d->field4 = 4275451258;
d->field5[0] = 3089026750;
d->field5[1] = 3619447220;
d->field5[2] = 409496463;
d->field5[3] = 3070974784;
d->field5[4] = 3770746185;
d->field5[5] = 1824038642;
d->field5[6] = 2365466585;
d->field5[7] = 2202808331;
d->field6 = 330349589;
}
Результат вывода массива байт на экран:
E4 52 24 61 00 00 00 00
8E 9E B1 B0 50 04 8E 5D
7A 35 D6 FE BE CE 1E B8
B4 61 BC D7 8F 6B 68 18
40 5B 0B B7 49 05 C1 E0
F2 9E B8 6C D9 27 FE 8C
0B 30 4C 83 00 00 00 00
15 BC B0 13