Содержание


Упорядочивание приложений – что означают для вас различия в порядке следования байтов в компиляторе IBM XL C/C++

Comments

При миграции с компилятора, использующего прямой порядок байтов, на компилятор с обратным порядком байтов могут потребоваться некоторые изменения исходного кода для сохранения первоначального поведения программы или результатов ее работы. Векторы, связь по памяти между элементами различного размера, вещественные числа двойной точности, комплексные числа и сериализация – все эти аспекты исходного кода необходимо пересмотреть при портировании такого рода. Дистрибутивы Linux® с обратным порядком байтов на IBM Power Systems™ и дистрибутивы с прямым порядком используют разные ABI. Программы, имеющие зависимости со старым ABI, необходимо обновить. Новые встроенные функции компиляторов облегчают портирование в аспектах, касающихся порядка байтов в векторах.

В статье рассматриваются проблемы, которые могут возникать при портировании кода на C/C++ с прямым порядком байтов в код с обратным порядком на IBM XL C/C++ для Power Systems. Также приводятся рекомендации по изменению написания кода и описываются возможности и параметры компилятора, облегчающие портирование кода.

Сравнение прямого и обратного порядка байтов

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

На платформах с прямым порядком следования байтов ("big endian") байты в памяти располагаются так, что первым ("левым") является старший байт. На платформах с обратным порядком следования байтов ("little endian") первым ("левым") является младший байт.

Например, на рисунке 1 изображен формат хранения числа 000102030405060716 (рассматриваемого как 8-байтное целое) в памяти на платформах с прямым и обратным порядком байтов. На этом рисунке a представляет собой адрес ячейки памяти.

Рисунок 1. Представление прямого и обратного порядка байтов в памяти
Рисунок 1. Представление прямого и обратного порядка байтов в памяти
Рисунок 1. Представление прямого и обратного порядка байтов в памяти

Векторы

Архитектура процессора IBM POWER® поддерживает 16-байтные векторы, содержащие шестнадцать 1-байтных элементов, восемь 2-байтных, четыре 4-байтных или два 8-байтных. Процессор имеет 128-битные векторные регистры и поддерживает инструкции для загрузки векторов в регистры, операций с векторами в регистрах и выгрузки векторных регистров в оперативную память. Компиляторы IBM XL предоставляют поддержку встроенных функций для работы с векторами на уровне языка программирования.

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

Порядок следования элементов вектора и порядок следования байтов элемента вектора

Существует два способа размещения элементов вектора в векторном регистре. Можно загрузить элементы, начиная с самого младшего и заканчивая самым старшим, так чтобы элемент 0 был в векторном регистре самым левым элементом. Другой способ – загрузить элементы, начиная с самого старшего и заканчивая самым младшим, так чтобы элемент 0 был в векторном регистре самым правым элементом. Первое расположение называется прямым порядком следования элементов вектора, второе – обратным.

При прямом порядке байтов всегда используется прямой порядок элементов вектора.

При обратном порядке байтов можно выбрать один из двух вариантов: прямой порядок элементов вектора и обратный порядок элементов вектора.

Независимо от порядка следования элементов вектора элементы вектора при прямом порядке используют в памяти прямой порядок байтов. Элементы вектора при обратном порядке по умолчанию используют обратный порядок байтов в памяти.

На рисунках 2 и 3 показана разница между прямым и обратным порядком следования элементов вектора.

На рисунке 2 изображено представление 16-байтного вектора 000102030405060708090A0B0C0D0E0F16 в векторном регистре при прямом порядке следования байтов. Обозначения b127 и b0 на рисунке отмечают 127-й и 0-й бит регистра соответственно. На рисунке показаны представления векторов, заполненных элементами длиной 1, 2, 4 и 8 байтов соответственно сверху вниз.

Рисунок 2. Представления прямого порядка следования элементов вектора в векторных регистрах
Рисунок 2. Представления прямого порядка следования элементов вектора в векторных регистрах
Рисунок 2. Представления прямого порядка следования элементов вектора в векторных регистрах

На рисунке 3 изображено представление 16-байтного вектора 000102030405060708090A0B0C0D0E0F16 в векторном регистре при обратном порядке следования байтов. Обозначения b127 и b0 на рисунке отмечают 127-й и 0-й бит регистра соответственно. На рисунке показаны представления векторов, заполненных элементами длиной 1, 2, 4 и 8 байтов соответственно сверху вниз.

Рисунок 3. Представления обратного порядка следования элементов вектора в векторных регистрах
Рисунок 3. Представления обратного порядка следования элементов вектора в векторных регистрах
Рисунок 3. Представления обратного порядка следования элементов вектора в векторных регистрах

Параметр -qaltivec

Параметр -qaltivec позволяет указать компилятору, использующему обратный порядок байтов, в каком порядке размещать элементы вектора в векторных регистрах.

При указании -qaltivec=le компилятор загружает векторы в обратном порядке элементов и предполагает, что векторы загружаются на хранение с обратным порядком элементов. При необходимости компилятор вставляет операции перестановки элементов вектора, чтобы гарантировать использование встроенными функциями load и store обратного порядка элементов. Для встроенных функций работы с векторами, обращающихся к конкретным элементам, компилятор предполагает, что векторы были загружены с обратным порядком элементов. При обратном порядке байтов параметр -qaltivec=le используется по умолчанию.

При указании -qaltivec=be компилятор загружает векторы в прямом порядке элементов и предполагает, что векторы загружаются на хранение с прямым порядком элементов. При необходимости компилятор вставляет операции перестановки элементов вектора, чтобы гарантировать использование встроенными функциями load и store прямого порядка элементов. Для встроенных функций работы с векторами, обращающихся к конкретным элементам, компилятор предполагает, что векторы были загружены с прямым порядком элементов.

Для демонстрации вышесказанного рассмотрим следующую программу:

#include<stdio.h>
union Example
{
 vector signed int vint;
 int sint[4];
};

int main() {
 union Example example;

 example.sint[0] = 0x0102;
 example.sint[1] = 0x0304;
 example.sint[2] = 0x0506;
 example.sint[3] = 0x0708;

 printf("First vector element: %04x\n", vec_extract(example.vint,0));
 printf("Second vector element: %04x\n", vec_extract(example.vint,1));
 printf("Third vector element: %04x\n", vec_extract(example.vint,2));
 printf("Fourth vector element: %04x\n", vec_extract(example.vint,3));

 return 0;
}

Если эта программа компилируется на платформе с прямым порядком байтов или на платформе с обратным порядком байтов с использованием параметра -qaltivec=le, выводится следующая информация:

First vector element: 0102
Second vector element: 0304
Third vector element: 0506
Fourth vector element: 0708

Если программа компилируется на платформе с обратным порядком байтов с использованием параметра -qaltivec=be, выводится следующая информация:

First vector element: 0708
Second vector element: 0506
Third vector element: 0304
Fourth vector element: 0102

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

Более переносимым способом работы с векторами является их загрузка и выгрузка с использованием рассмотренных в следующем разделе встроенных функций vec_xl, vec_xl_be, vec_xst и vec_xst_be.

Рассмотрим следующую программу:

#include<stdio.h>

int main() {
 vector signed int v1;
 vector signed int v2;
 vector signed int v3;
 vector signed int v4;
 int a[4];

 v1 = vec_xl(0, (int[]){1,2,3,4});
 vec_xst(v1, 0, a);
 printf("v1 via a: %d %d %d %d\n", a[0],a[1],a[2],a[3]);

 v2 = vec_neg(v1);
 vec_xst(v2, 0, a);
 printf("v2 via a: %d %d %d %d\n", a[0],a[1],a[2],a[3]);

 //Объединить старший и младший в зависимости от порядка элементов вектора
 v3 = vec_mergeh(v1, v2);
 vec_xst(v3, 0, a);
 printf("v3 via a: %d %d %d %d\n", a[0],a[1],a[2],a[3]);

 v4 = vec_mergel(v1, v2);
 vec_xst(v4, 0, a);
 printf("v4 via a: %d %d %d %d\n", a[0],a[1],a[2],a[3]);

 return 0;
}

Выводимые результаты одинаковы на обеих платформах (и с прямым, и с обратным порядком байтов). Выводится следующая информация:

v1 via a: 1 2 3 4
v2 via a: -1 -2 -3 -4
v3 via a: 1 -1 2 -2
v4 via a: 3 -3 4 -4

Встроенные векторные функции vec_xl, vec_xst, vec_mergeh и vec_mergel учитывают порядок элементов вектора. Другими словами, при компиляции программы на платформе с обратным порядком байтов и с параметром -qaltivec=le:

  • vec_xl использует инструкцию load из набора Vector Scalar eXtension (VSX), которая всегда загружает векторы с прямым порядком элементов. Затем используется инструкция перестановки вектора, переворачивающая вектор в регистре в соответствии с принятым обратным порядком элементов.
  • vec_xst предполагает, что вектор в регистре использует обратный порядок элементов, поэтому используется инструкция перестановки для преобразования в прямой порядок элементов вектора. Затем используется инструкция store из набора VSX, всегда выполняющая выгрузку вектора в память в прямом порядке элементов.
  • vec_mergeh знает, что элементы вектора начинаются справа. Регистры вектора, содержащие v1 и v2, выглядят следующим образом:
v1	 4	 3	 2	 1
v2	-4	-3	-2	-1

Поскольку vec_mergeh начинает считать справа, она корректно использует 1 и 2 для элементов 0 и 2 при выполнении vec_mergeh(v1, v2).

  • Аналогично, vec_mergel знает, что элементы вектора начинаются справа. Следовательно, она корректно использует -1 и -2 для элементов 1 и 3 при выполнении vec_mergel(v1, v2).

При компиляции программы на платформе с прямым порядком байтов или при компиляции ее на платформе с обратным порядком байтов и с параметром – -qaltivec=be:

  • vec_xl использует инструкцию load из VSX, которая всегда загружает векторы с прямым порядком элементов. Нет необходимости вставлять инструкцию перестановки вектора.
  • vec_xst предполагает, что вектор в регистре использует прямой порядок элементов. Поэтому она непосредственно использует инструкцию store из VSX, которая всегда выполняет выгрузку вектора в память с прямым порядком элементов.
  • vec_mergeh знает, что элементы вектора начинаются слева. Регистры вектора, содержащие v1 и v2, выглядят следующим образом:
v1	 1	 2	 3	 4
v2	-1	-2	-3	-4

Поскольку vec_mergeh начинает считать слева, она корректно использует 1 и 2 для элементов 0 и 2 при выполнении vec_mergeh(v1, v2).

  • Аналогично, vec_mergel знает, что элементы вектора начинаются слева. Поэтому она корректно использует -1 и -2 для элементов 1 и 3 при выполнении vec_mergel(v1, v2).

Для программ, не использующих unions, параметр –qaltivec=be может быть полезен при портировании кода с прямого порядка байтов в обратный.

Криптографические встроенные функции POWER8 требуют, чтобы входные векторы использовали прямой порядок элементов вектора. Это можно сделать либо при помощи параметра -qaltivec=be, либо при помощи функций vec_xl_be и vec_xst_be для загрузки и выгрузки. Эти функции загрузки и выгрузки векторов рассматриваются в следующем разделе.

Новые встроенные функции загрузки и выгрузки векторов

В компилятор были добавлены новые встроенные функции загрузки и выгрузки векторов, использующие VSX-инструкции. Отметим, что компилятор XL C/C++ для Linux on Power Systems с обратным порядком байтов является 64-разрядным, поэтому применяются только прототипы для 64-разрядного режима. Эти встроенные функции более подробно описаны в справочном руководстве по компилятору XL C/C++ (библиотека документации XL C/C++ for Linux).

vec_xl(offset, address)
Эта функция загружает 16-байтный вектор из адреса оперативной памяти, указанного смещением offset и адресом address, с соответствующим порядком элементов для используемой платформы и параметром -qaltivec.

Прототипы (64-разрядный режим):

vector signed char vec_xl(long, signed char *);
vector unsigned char vec_xl(long, unsigned char *);
vector signed short vec_xl(long, signed short *);
vector unsigned short vec_xl(long, unsigned short *);
vector signed int vec_xl(long, signed int *);
vector unsigned int vec_xl(long, unsigned int *);
vector signed long long vec_xl(long, signed long long *);
vector unsigned long long vec_xl(long, unsigned long long *);
vector float vec_xl(long, float *);
vector double vec_xl(long, double *);

vec_xl_be(offset, address)
Эта функция загружает 16-байтный вектор из адреса оперативной памяти, указанного смещением offset и адресом address, с прямым порядком независимо от платформы или параметра -qaltivec.

Прототипы (64-разрядный режим):

vector signed char vec_xl_be(long, signed char *);
vector unsigned char vec_xl_be(long, unsigned char *);
vector signed short vec_xl_be(long, signed short *);
vector unsigned short vec_xl_be(long, unsigned short *);
vector signed int vec_xl_be(long, signed int *);
vector unsigned int vec_xl_be(long, unsigned int *);
vector signed long long vec_xl_be(long, signed long long *);
vector unsigned long long vec_xl_be(long, unsigned long long *);
vector float vec_xl_be(long, float *);
vector double vec_xl_be(long, double *);

vec_xst(vect, offset, address)
Эта функция выгружает элементы 16-байтного вектора, указанного аргументом vect, в данный адрес памяти. Адрес вычисляется путем добавления значения смещения, указанного в offset, к адресу памяти, указанному в address, с соответствующим порядком элементов для используемой платформы и параметром -qaltivec.

Прототипы (64-разрядный режим):

void vec_xst(vector signed char, long, signed char *);
void vec_xst(vector unsigned char, long, unsigned char *);
void vec_xst(vector signed short, long, signed short *);
void vec_xst(vector unsigned short, long, unsigned short *);
void vec_xst(vector signed int, long, signed int *);
void vec_xst(vector unsigned int, long, unsigned int *);
void vec_xst(vector signed long long, long, signed long long *);
void vec_xst(vector unsigned long long, long, unsigned long long *);
void vec_xst(vector float, long, float *);
void vec_xst(vector double, long, double *);

vec_xst_be(vect, offset, address)
Эта функция выгружает элементы 16-байтного вектора, указанного аргументом vect, в данный адрес памяти. Адрес вычисляется путем добавления значения смещения, указанного в offset, к адресу памяти, указанному в address, с прямым порядком элементов независимо от используемой платформы или параметра -qaltivec.

Прототипы (64-разрядный режим):

void vec_xst_be(vector signed char, long, signed char *);
void vec_xst_be(vector unsigned char, long, unsigned char *);
void vec_xst_be(vector signed short, long, signed short *);
void vec_xst_be(vector unsigned short, long, unsigned short *);
void vec_xst_be(vector signed int, long, signed int *);
void vec_xst_be(vector unsigned int, long, unsigned int *);
void vec_xst_be(vector signed long long, long, signed long long *);
void vec_xst_be(vector unsigned long long, long, unsigned long long *);
void vec_xst_be(vector float, long, float *);
void vec_xst_be(vector double, long, double *);

Векторные литералы и двоично-кодированные десятичные числа (BCD

Встроенные функции для двоично-кодированных десятичных чисел (binary-coded decimal – BCD) работают с BCD-значениями со знаком, загруженными в векторные регистры. Каждое BCD-значение состоит из нескольких 4-битных полубайтов, содержащих цифры от 0 и 9, а последний полубайт содержит знак. Например, значение 10 представляется (в 4-битных полубайтах) следующим образом:

0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0

Каждое шестнадцатеричное значение здесь представляет 4-битный полубайт. Полубайт 0x1 – это 1 в числе 10, а полубайт 0x0 после него – это 0 в числе 10. Полубайт 0xC – это специальное значение, представляющее знак плюс. Поскольку тип данных BCD и BCD-числа не нужно загружать в векторные регистры, встроенные BCD-функции используют в качестве аргументов и результатов тип vector unsigned char. Это делает встроенные BCD-функции зависимыми от порядка элементов в векторе, хотя BCD-числа сами по себе не являются векторами. Иными словами, векторы, содержащие BCD-числа, должны загружаться в прямом порядке элементов вектора даже при компиляции с параметром –qaltivec=le. Это является проблемой при статической инициализации векторов (например, с использованием векторных литералов): прикрепленное выражение инициализатора статического вектора с элементами, перечисленными в прямом порядке, заполняет векторный регистр в обратном порядке элементов на платформах с обратным порядком.

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

#include <stdio.h>
#if __LITTLE_ENDIAN__
#define BCD_INIT(b0, b1, b2, b3, b4, b5, b6, b7, \
                 b8, b9, ba, bb, bc, bd, be, bf) \
     { bf, be, bd, bc, bb, ba, b9, b8, b7, b6, b5, b4, b3, b2, b1, b0 }
#else
#define BCD_INIT(b0, b1, b2, b3, b4, b5, b6, b7, \
                 b8, b9, ba, bb, bc, bd, be, bf) \
     { b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf }
#endif

vector unsigned char v1001 =
  BCD_INIT(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
           0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1C);

vector unsigned char v9009 =
  BCD_INIT(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
           0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x9C);

static void print_bytes(vector unsigned char v)
{
  long i;
  unsigned char b[16];
  vec_xst_be(v, 0, b);
  printf("%.02hhx", b[0]);
  for (i = 1; i < 16; ++i)
  {
    printf(", %.02hhx", b[i]);
  }
  printf("\n");
}

int main(void)
{
  vector unsigned char result;
  printf("Adding statically initialized vectors\n");
  printf("op1 is    ");  print_bytes(v1001);
  printf("op2 is    ");  print_bytes(v9009);
  result = __bcdadd(v1001, v9009, 0);
  printf("result is ");  print_bytes(result);
  return 0;
}

Эта программа выводит следующие данные на обеих платформах (и с прямым, и с обратным порядком байтов):

Adding statically initialized vectors
op1 is    00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 10, 00, 1c
op2 is    00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 90, 00, 9c
result is 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 01, 0c

Макрос __LITTLE_ENDIAN__ предопределен только на платформах с обратным порядком следования байтов. Мы создаем макрос BCD_INIT для изменения порядка следования байтов в инициализаторе. Отметим, что функция print_bytes использует встроенную функцию vec_xst_be, поскольку векторы используют прямой порядок элементов.

Другими способами обойти проблему являются инициализация во время исполнения при помощи встроенной функции vec_xl_be или изменение порядка следования элементов статически инициализируемого вектора во время исполнения при помощи встроенной функции vec_reve.

Двоичный интерфейс приложений (ABI)

Компилятор XL C/C++ для Linux on Power Systems с обратным порядком байтов использует новую спецификацию IBM Power Architecture® 64-bit ELF V2 ABI. Этот новый ABI улучшает несколько аспектов, в том числе вызовы функций. Однако это означает, что файлы сборки, разработанные для старого ABI, необходимо портировать на новый ABI. Программы на Fortran, C и C++, придерживающиеся стандартов соответствующего языка, не требуют портирования на новый ABI. Программы, содержащие нестандартные расширения, необходимо проанализировать на чувствительность к ABI.

Связь по памяти между элементами различного размера

При портировании программы с прямым порядком байтов на платформу с обратным порядком байтов необходимо учитывать связь по памяти между элементами различного типа. В C/C++ это касается объединений (union) и приведения типов указателей. Эти элементы подробно рассматриваются в следующих разделах.

Обратите внимание, что шестнадцатеричные значения всегда выводятся с прямым порядком байтов.

Объединения

При использовании объединений, состоящих из элементов разного размера, результаты работы программы могут быть разными в зависимости от порядка следования байтов. Для демонстрации этого рассмотрим следующую программу:

#include<stdio.h>

union Example
{
  char i;
  short j;
  int k;
};

main()
{
  union Example example;

  example.k = 0x01020304;

  printf("example.i: %02x\n", example.i);
  printf("example.j: %04x\n", example.j);
  printf("example.k: %08x\n", example.k);

  return 0;
}

В этой программе мы указали, что i, j и k используют общую память, имея при этом разный размер. При прямом порядке следования байтов старший байт расположен в ячейке памяти с наименьшим адресом (или слева), т.е. i, j и k содержат следующие данные:

example.i: 01
example.j: 0102
example.k: 01020304

На платформах с обратным порядком следования байтов слева располагается младший байт, т.е. i, j и k содержат следующие данные:

example.i: 04
example.j: 0304
example.k: 01020304

Приведение типов указателей

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

#include<stdio.h>

int main() {
  int i = 0x01020304;  
  printf("example.i: %02x\n", *(char*)&i);
  printf("example.j: %04x\n", *(short*)&i);
  printf("example.k: %08x\n", *(int*)&i);
  return 0;
}

Обратите внимание, что данный код некорректен и нарушает правила языка относительно использованию приведения типов (например, integer не может обрабатываться как short); мы приводим его просто для демонстрации проблемы.

В этой программе приведение типов указателей используется для приведения типа integer в char, short и int. При прямом порядке следования байтов старший байт переменной i расположен в ячейке памяти с наименьшим адресом. На этой платформе результат работы программы будет таким:

example.i: 01
example.j: 0102
example.k: 01020304

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

example.i: 04
example.j: 0304
example.k: 01020304

Типы long double и complex

В XL C/C++ тип long double состоит из двух частей типа double, описывающих два слагаемых числа, имеющих разный порядок величины, которые не пересекаются (за исключением нулевых или близких к нулю значений). Значение старшего double, которое идет в памяти первым, должно иметь большую величину даже при обратном порядке байтов. Значение числа long double является суммой двух его real-частей.

Комплексные числа составляются из действительной и мнимой частей, причем действительная часть всегда идет до мнимой. В C/C++ к действительной и мнимой частям можно обратиться при помощи унарных операторов __real__ и __imag__ или наборов функций creal и cimag.

Рассмотрим следующую программу:

#include<stdio.h>
#include<complex.h>

union Example
{
 float f[2];
 float complex c;
};

int main()
{
 union Example example;
 example.c = 1.0f + 0.0f*I;

 printf("First element of float: %.4f\n", example.f[0]);
 printf("Second element of float: %.4f\n", example.f[1]);
 printf("Real part of complex: %.4f\n", __real__(example.c));
 printf("Imaginary part of complex: %.4fi\n", __imag__(example.c));

 return 0;
}

И при прямом, и при обратном порядке байтов первым элементом f является 1.0000, а вторым – 0.0000. Действительной частью комплексного числа c является 1.0000, а мнимой – 0.0000.

First element of float: 1.0000
Second element of float: 0.0000
Real part of complex: 1.0000
Imaginary part of complex: 0.0000i

Порядок следования байтов элементов вещественных и комплексных чисел различен для прямого и обратного порядка байтов, но порядок элементов одинаков.

Сериализация

Бинарные файлы зависят от порядка байтов данных. При чтении на платформе с обратным порядком байтов бинарного файла данных, сгенерированного на платформе с прямым порядком байтов необходимо выполнить преобразование данных. Для изменения порядка байтов можно использовать встроенные функции __load2r, __load4r, __load8r, __store2r, __store4r и __store8r. Рассмотрим, например, следующую программу, которая, будучи скомпилированной и запущенной на платформе с прямым порядком байтов, генерирует бинарный файл bigendian.data:

$ cat writefile.c
#include <stdlib.h>
#include <stdio.h>
#include "data.h"
int main() {
  FILE *fp;
  data_t data = { {1,2,3,4,5,6,7,8}, {1,2,3,4}, {-1,-2}, 3.0, 4.0, "abcdefgh" };
  size_t res;
  fp = fopen("bigendian.data", "w");
  if (fp == NULL) {
    perror("fopen");
    exit(1);
  }
  res = fwrite(&data, sizeof(data), 1, fp);
  if (res != 1) {
    perror("fwrite");
    exit(1);
  }
  fclose(fp);
  printf("Wrote:\n");
  print_data(data);
  return 0;
}

$ cat data.h
#ifndef DATA_H
#define DATA_H
#include <stdint.h>
typedef struct {
  int64_t ll[8];
  int32_t i[4];
  int16_t s[2];
  float f;
  double d;
  char c[10];
} data_t;

static void print_data(data_t data) {
  long i;
  printf("data: ll={ ");
 
  for (i = 0; i < 8; ++i) {
    printf("0x%016lx ", data.ll[i]);
  }
  printf("}\n");
  printf("       i={ ");
  for (i = 0; i < 4; ++i) {
    printf("0x%08x ", data.i[i]);
  }
  printf("}\n");
  printf("       s={ ");
  for (i = 0; i < 2; ++i) {
    printf("0x%04hx ", data.s[i]);
  }
  printf("}\n");
  printf("       f=%f\n", data.f);
  printf("       d=%f\n", data.d);
  printf("       c=");
  i = 0;
  while(i < 10 && data.c[i] != '\0') {
    printf("%c", data.c[i]);
    ++i;
  }
  printf("\n");
}
#endif

fwrite записывает данные как поток байтов. В результате ll, i, s, f и d будут располагаться с прямым порядком байтов. Для чтения этого файла данных на платформе с обратным порядком байтов процедура чтения не может просто использовать fread. Необходимо также выполнить преобразование данных. Например:

$ cat readfile.c
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "data.h"
static void print_data(data_t data);
int main() {
  FILE *fp;
  data_t data;
  size_t res;
  fp = fopen("bigendian.data", "r");
  if (fp == NULL) {
    perror("fopen");
    exit(1);
  }
  /* Этот вызов fread будет читать все несимвольные данные 
     неправильно */
  res = fread(&data, sizeof(data), 1, fp);
  if (res != 1) {
    perror("fread");
    exit(1);
  }
  printf("Read:\n");
  print_data(data);
  /* Преобразование порядка байтов */
  {
    union {
      uint64_t u64;
      uint32_t u32;
      double d;
      float f;
    } tmp;
    long i;
    /* преобразование long long */
    for (i = 0; i < 8; ++i) {
      data.ll[i] = __load8r((uint64_t *) &data.ll[i]);
    }
    /* преобразование integer */
    for (i = 0; i < 4; ++i) {
      data.i[i] = __load4r((uint32_t *) &data.i[i]);
    }      
    /* преобразование short */
    for (i = 0; i < 2; ++i) {
      data.s[i] = __load2r((uint16_t *) &data.s[i]);
    }
    /* преобразование float */
      tmp.f = data.f;
      __store4r(tmp.u32, &data.f);
    /* преобразование double data */
    tmp.d = data.d;
    __store8r(tmp.u64, &data.d);
  }
  printf("After conversion:\n");
  print_data(data);
  return 0;
}

После компиляции и запуска readfile.c на платформе с обратным порядком байтов получатся следующие результаты:

Read:
data: ll={ 0x0100000000000000 0x0200000000000000 0x0300000000000000 0x0400000000000000 
0x0500000000000000 0x0600000000000000 0x0700000000000000 0x0800000000000000 }
       i={ 0x01000000 0x02000000 0x03000000 0x04000000 }
       s={ 0xffff 0xfeff }
       f=0.000000
       d=0.000000
       c=abcdefgh
After conversion:
data: ll={ 0x0000000000000001 0x0000000000000002 0x0000000000000003 0x0000000000000004 
0x0000000000000005 0x0000000000000006 0x0000000000000007 0x0000000000000008 }
       i={ 0x00000001 0x00000002 0x00000003 0x00000004 }
       s={ 0xffff 0xfffe }
       f=3.000000
       d=4.000000
       c=abcdefgh

Аналогичные соображения следует принимать во внимание при портировании алгоритмов, зависящих от порядка байтов данных. Например, функция, которая читает текстовый файл, содержащий шестнадцатеричную строку произвольной длины, и преобразовывает его в массив uint64_t, может привести тип символьной строки в uint64_t *. Хотя программа нарушает правила ANSI по перекрытию объектов в памяти, она работает корректно на платформах с прямым порядком байтов, поскольку массив char[8] и uint64_t совпадают. Это не так для платформ с обратным порядком байтов.

Загрузка и выгрузка в противоположном порядке байтов

XL C/C++ предоставляет следующие встроенные функции для преобразования порядка следования байтов:

unsigned short __load2r(unsigned short* address)
Загружает unsigned short из address с противоположным порядком байтов.

unsigned int __load4r(unsigned int* address)
Загружает unsigned integer из address с противоположным порядком байтов.

unsigned long long __load8r(unsigned long long* address)
Загружает unsigned long long из address с противоположным порядком байтов.

void __store2r(unsigned short source, unsigned short* address)
Выгружает unsigned short source в address с противоположным порядком байтов.

void __store4r(unsigned int source, unsigned int* address)
Выгружает unsigned integer source в address с противоположным порядком байтов.

void __store8r(unsigned long long source, unsigned long long* address)
Выгружает unsigned long long source в address с противоположным порядком байтов.

vec_revb(address)
Возвращает вектор такого же типа, что и адрес, содержащий байты соответствующего элемента address, с противоположным порядком байтов.

Прототипы:

vector signed char vec_revb(vector signed char);
vector unsigned char vec_revb(vector unsigned char);
vector signed short vec_revb(vector signed short);
vector unsigned short vec_revb(vector unsigned short);
vector signed int vec_revb(vector signed int);
vector unsigned int vec_revb(vector unsigned int);
vector signed long long vec_revb(vector signed long long);
vector unsigned long long vec_revb(vector unsigned long long);
vector float vec_revb(vector float)
vector double vec_revb(vector double);

vec_reve(address)
Возвращает вектор такого же типа, что и адрес, содержащий элементы address, с противоположным порядком байтов.

Прототипы:

vector signed char vec_reve(vector signed char);
vector unsigned char vec_reve(vector unsigned char);
vector signed short vec_reve(vector signed short);
vector unsigned short vec_reve(vector unsigned short);
vector signed int vec_reve(vector signed int);
vector unsigned int vec_reve(vector unsigned int);
vector signed long long vec_reve(vector signed long long);
vector unsigned long long vec_reve(vector unsigned long long);
vector float vec_reve(vector float);
vector double vec_reve(vector double);

Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=1014999
ArticleTitle=Упорядочивание приложений – что означают для вас различия в порядке следования байтов в компиляторе IBM XL C/C++
publish-date=09152015