Створення коду, незалежного від порядку байтів, на мові C (исходники), Різне, Програмування, статті

Введення


Для засвоєння концепції порядку байтів, не обов’язково розбиратися в логічних схемах організації пам’яті – потрібно тільки розуміти, що пам’ять є один великий масив. Масив містить байти. Програмісти використовують адреса для вказівки на місце розташування необхідного масиву в пам’яті.







 



Порядок байтів

Цей термін означає, як зберігає і використовує байти система: порядок від старшого до молодшого (запис починається зі старшого байта і закінчується молодшим, big endian), порядок від молодшого до старшого (запис інформації починається з молодшого і закінчується старшим, little endian).


Кожен адреса вказує на один елемент масиву. Кожен елемент є зазвичай одним байтом. У деяких конфігураціях пам’яті адресу зберігає іншу інформацію крім байта, однак така реалізація зараз зустрічається досить рідко, тому будемо вважати, що всі адреси пам’яті зберігають тільки байти.


Зберігання байтів в пам’яті


Ми будемо оперувати 32-ма бітами, тобто чотирма байтами. Цілі числа або числа з плаваючою точкою із звичайною точністю записуються в 32 бітах. Але оскільки кожен адреса в пам’яті може зберігати тільки один байт, а не чотири, то 32-х бітне число треба розбити на 4 байта. Наприклад, припустимо, що є 32-х бітне число, записане як 12345678, яке є шістнадцятковим. Оскільки кожна шістнадцяткова цифра представляється чотирма байтами, то необхідно вісім шістнадцятиричних чисел для подання даного 32-х бітного значення. Чотири байта це: 12, 34, 56, і 78. Є два способи зберігати це в пам’яті, як показано нижче.



Зверніть увагу на попередню таблицю – числа знаходяться в зворотному порядку. Для запам’ятовування порядків корисно наступне правило: молодший байт записується першим (порядок “від молодшого до старшого” – little-endian), старший байт записується першим (порядок “від старшого до молодшого” – big-endian).


Регістри і порядок байтів


Порядок байтів має значення тільки тоді, коли потрібно розбити багатобайтове послідовність і записати отримані байти в послідовні ділянки пам’яті. Однак якщо є 32-х бітний регістр, який зберігає 32-х бітне значення, то немає сенсу говорити про порядок розміщення байтів в пам’яті. Регістр не чутливий до порядку “від старшого до молодшого” або “від молодшого до старшого”; регістр тільки зберігає 32-х бітне значення. Вкрай правий біт є молодшим, вкрай лівий біт є старшим.


Деякі люди вважають, що регістр має порядок “від старшого до молодшого”, тому що він записує старші байти в молодші адреси пам’яті.


Важливість порядку байтів


Порядок байтів є атрибутом системи, який визначає, представляються чи цілі числа зліва-направо або справа-наліво. У сьогоднішньому світі віртуальних машин і гігагерцевий процесорів багато програмістів замислюються, чому вони повинні враховувати питання, що розглядається в цій статті? На жаль, необхідно враховувати порядок байтів кожен раз при розробці апаратного або програмного забезпечення. І, в общем-то, немає універсального правила про те, який порядок байтів використовувати.


Всі процесори мають бути розроблені з урахуванням або порядку “від молодшого до старшого” або “від старшого до молодшого”. Наприклад, процесори Intel ® сімейства 80×86 і їх клони використовують “від молодшого до старшого”, в той час як процесори Sun SPARC, Motorola 68K, і PowerPC ® використовують порядок “від старшого до молодшого”.


Чому порядок байт так важливий? Уявіть, що потрібно записувати цілочисельні значення в файл, і послати цей файл комп’ютера, який використовує протилежний порядок читання байтів; цей комп’ютер прочитає записане вам в файл значення невірно. Це відбувається через різні порядків розміщення байтів в пам’яті; в цьому випадку треба читати значення задом наперед для його коректності.


Порядок запису байт в пам’ять являє собою велику проблему при пересиланні через мережу. Ще раз повторю, що якщо послати число, створене в одному порядку запису байтів, на комп’ютер, що використовує протилежний порядок запису, то виникне проблема. Ситуація ускладнюється ще більше, якщо невідомий порядок запису байтів на віддаленій машині.


Приклад 1 показує, до чого призводить програмування без урахування відмінностей у напрямку записи байтів.


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





#include <stdio.h>
#include <string.h>
int main (int argc, char* argv[]) {
FILE* fp;
/* Our example data structure */
struct {
char one[4];
int two;
char three[4];
} data;
/* Fill our structure with data */
strcpy (data.one, “foo”);
data.two = 0x01234567;
strcpy (data.three, “bar”);
/* Write it to a file */
fp = fopen (“output”, “wb”);
if (fp) {
fwrite (&data, sizeof (data), 1, fp);
fclose (fp);
}
}

Наведений вище код коректно скомпілюйте на будь-якому комп’ютері. Однак виведений результат буде різним за різних порядків записи байтів в пам’ять. Висновок програми після перегляду його утилітою hexdump, Буде таким, як у прикладах 2 і 3.


Приклад 2. Висновок команди hexdump-C на комп’ютері з порядком “від старшого до меншого”





00000000 66 6f 6f 00 12 34 56 78 62 61 72 00 /foo..4Vxbar./
0000000c

Приклад 3. Висновок команди hexdump-C з порядком запису “від молодшого до старшого”





00000000 66 6f 6f 00 78 56 34 12 62 61 72 00 /foo.xV4.bar./
0000000c


Вплив порядку зберігання байт в пам’яті на програмний код


Різниця в порядку байтів далеко не завжди впливає на результат операцій. Якщо виконуються побітові операції або операції побітового зсуву на цілочисельних значеннях, то не треба враховувати порядок байтів в пам’яті. Комп’ютер впорядковує численні байти таким чином, що молодший байт завжди залишається самим молодшим, старший байт завжди є старшим.


Природно в такому випадку задатися питанням, чи можуть рядки зберігатися в якому-небудь незвичайному порядку, характером для конкретного комп’ютера. Для відповіді на це питання, повернемося до основ організації масивів. Рядок у мові C являє собою масив символів. Кожен символ вимагає одного байта пам’яті, за умови, що символи відображаються в кодуванні ASCII. У масиві адресу послідовних елементів зростає. Таким чином, адреса & arr [i] менше ніж адресу & arr [i +1]. Хоча це і не є очевидним, якщо що-небудь зберігається в комірках пам’яті, адреси яких послідовно збільшуються, воно буде записано в файл в такий же зростаючій послідовності. При запису в файл зазвичай задається адреса в пам’яті і число байтів, яке потрібно записати у файл, починаючи з цієї адреси.


Припустимо, є рядок man. Будемо вважати, що m зберігається за адресою 1000, a за адресою 1001, і n за адресою 1002. Символ кінця рядка можна легко розрізняти окремі байти в рядку. Індексація масивів використовується для забезпечення доступу до байтів (символам) рядки, не можна просто звертатися до окремих байтам типів int або long – для цього треба використовувати покажчики. Окремі байти в int або long в більшій чи меншій мірі приховані від програміста.


Тепер уявімо, що цей рядок потрібно записувати в файл за допомогою методу типу write(). Ставимо покажчик на m і задаємо число байтів, яке потрібно записати у файл (в даному випадку чотири). Метод write() обробляє байт за байтом і записує їх у файл починаючи з m і закінчуючи символом кінця рядка.


Отже, ми довели, що порядок байтів не впливає на уявлення рядків у мові С.


Порядок байтів має значення, коли ви використовуєте приведення типів, що залежить від конкретного порядку байтів. Приклад такої ситуації показаний в прикладі 4, Але врахуйте, що існує багато різних привидів типів, які можуть викликати проблеми.


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





unsigned char endian[2] = {1, 0};
short x;
x = *(short *) endian;

Яким буде значення x? Давайте поглянемо, що робить цей код. Ми створили двобайтових масив, а потім перетворили його до типу short. Використовуючи масив, ми фактично змушуємо систему до використання будь-якого конкретного порядку байтів; давайте подивимося, як система обробить ці два байти.


У випадку, якщо використовується підхід “від молодшого до старшого”, 0 і 1 інтерпретуються задом наперед і будуть представлені як 0,1. Так як старший байт 0, а молодший байт 1, значення x буде рівним 1.


З іншого боку, в системі з порядком “від старшого до молодшого” старшим байтом буде 1 і значення змінної x буде рівним 256.


Визначення порядку байтів під час виконання програми


Один зі способів визначити порядок байтів в системі – це перевірити розташування в пам’яті визначеної константи. Нагадаємо, що розміщення одиниці (1) типу цілочисельного 32-х бітного числа буде наступним – 00 00 00 01 для порядку “від старшого до молодшого” і 01 00 00 00 для порядку “від молодшого до старшого”. Поглянувши на перший байт цієї константи, можна вказати порядок байтів у конкретної платформи, і потім вжити найбільш ефективне в даній ситуації дію.


Приклад 5 перевіряє перший байт цілого числа i для того, щоб визначити, чи є воно 0 або 1. Якщо воно дорівнює 1, поточна платформа використовує режим байтів в пам’яті “від молодшого до старшого”, а якщо 0 – то режим “від старшого до молодшого”.


Приклад 5. Визначення порядку байтів





const int i = 1;
#define is_bigendian() ( (*(char*)&i) == 0 )
int main(void) {
int val;
char *ptr;
ptr = (char*) &val;
val = 0x12345678;
if (is_bigendian()) {
printf( %X.%X.%X.%X
“, u.c[0], u.c[1], u.c[2], u.c[3]);
} else {
printf( %X.%X.%X.%X
“, u.c[3], u.c[2], u.c[1], u.c[0]);
}
exit(0);
}

Інший спосіб визначити порядок байтів полягає у використанні символьних покажчиків на байти в числі типу int і потім перевірити перший байт – є він 0 або 1. Приклад 6 ілюструє цей спосіб.


Приклад 6. Символьні вказівники





#define LITTLE_ENDIAN 0
#define BIG_ENDIAN 1
int endian() {
int i = 1;
char *p = (char *)&i;
if (p[0] == 1)
return LITTLE_ENDIAN;
else
return BIG_ENDIAN;
}

Мережевий порядок байтів


Мережеві стеки та протоколи також повинні визначати свою послідовність байтів, інакше два вузли мережі з різним порядком байтів просто не зможуть взаємодіяти. Це найбільш яскравий приклад впливу порядку байтів на програми. Всі рівні протоколу TCP / IP працюють в режимі “від старшого до молодшого”. Будь 16-ти або 32-х бітне значення всередині заголовків різних рівнів (таке як IP-адресу, довжина пакета, контрольна сума) повинні надсилатися і отримуватися так, щоб старший байт був першим.


Порядок байтів “від старшого до молодшого”, який використовується в протоколі TCP / IP, іноді ще називають мережевим порядком байтів. Навіть якщо комп’ютери в мережі використовують порядок “від молодшого до старшого”, багатобайтові цілочисельні значення для передачі їх по мережі повинні бути перетворені в мережевий порядок байтів, а потім ще раз перетворені назад в порядок “від молодшого до старшого” на приймаючому комп’ютері.


Припустимо, що потрібно встановити TCP-з’єднання з комп’ютером, чий IP-адреса дорівнює 192.0.1.2. IPv4 використовує унікальне 32-х бітове ціле число для ідентифікації кожного комп’ютера в мережі. Розділений точками IP-адреса має бути представлений як цілочисельного значення.


Наприклад, ПК на базі 80×86 зв’язується з сервером на базі SPARC через Інтернет. Без будь-яких додаткових дій з боку користувача процесор 80×86 може перетворити 192.0.1.2 в цілочисельне число з послідовністю байтів “від молодшого до старшого”, рівне 0x020100C0, і передати байти в послідовності 1 лютого 00 C0. SPARC отримає байти в порядку 1 лютого 00 C0, переведе байти в порядок “від старшого до молодшого “0x020100c0, і неправильно прочитає IP-адреса 2.1.0.192.


Якщо стек працює на процесорі, що використовує порядок байтів “від молодшого до старшого”, він повинен переупорядкувати під час виконання байти кожного багатобайтові значення з заголовків рівнів протоколу TCP / IP. Якщо стек працює в режимі байтів “від більшого до меншого”, то немає приводу для занепокоєнь. Щоб стек мав переносимістю (працював на процесорі обох типів), він повинен вміти приймати рішення про необхідність здійснення переупорядочивания байтів під час компіляції.


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



htons()
Змінювати порядок байти 16-ти бітового беззнакового значення з ладу, використовуваного в поточному процесорі, в мережевий порядок байтів. Назва макросу можна розшифрувати як “host to network short” (“порядок в беззнаковое короткому числі перетворити в мережевий порядок байтів “).
htonl()
Змінювати порядок байти 32-х бітного беззнакового значення з ладу, використовуваного в поточному процесорі, в мережевий порядок байтів. Назва макросу можна розшифрувати як “host to network long” (“порядок в беззнаковое довгому числі перетворити в мережевий порядок байтів “).
ntohs()
Змінювати порядок байти 16-ти бітного беззнакового значення з мережевого порядку байтів в порядок байтів, використовуваний на поточному процесорі. Назва макросу можна розшифрувати як “network to host short” (“З мережного порядку в порядок для використовуваного процесора, 16-ти бітне число”).
ntohl()
Змінювати порядок байти 32-х байтного беззнакового значення з мережевого порядку в порядок байтів, використовуваний на поточному процесорі. Назва макросу може бути розшифровано як “network to host long” (“З мережного порядку в порядок для використовуваного процесора, 32-ти бітне число”).

Розглянемо програму з приклад 7.


Приклад 7. Програма на мові С





#include <stdio.h>
main() {
int i;
long x = 0x112A380; /* Value to play with */
unsigned char *ptr = (char *) &x; /* Byte pointer */
/* Observe value in host byte order */
printf(“x in hex: %x
“, x);
printf(“x by bytes: “);
for (i=0; i < sizeof(long); i++)
printf(“%x “, ptr[i]);
printf(”
“);
/* Observe value in network byte order */
x = htonl(x);
printf(”
After htonl()
“);
printf(“x in hex: %x
“, x);
printf(“x by bytes: “);
for (i=0; i < sizeof(long); i++)
printf(“%x “, ptr[i]);
printf(”
“);
}

Ця програма показує, як зберігається мінлива x типу long, що зберігає значення 112A380 (шістнадцяткове).


Коли ця програма виконується на процесорі, що використовує порядок байтів “від молодшого до старшого”, вона виводить інформацію як в прикладі 8.


Приклад 8. Результат роботи програми на процесорі з режимом “від молодшого до старшого”





x in hex: 112a380
x by bytes: 80 a3 12 1
After htonl()
x in hex: 80a31201
x by bytes: 1 12 a3 80

Якщо подивитися на окремі байти x, то видно, що молодший байт (0x80) знаходиться за меншим адресою. Після цього викликати htonl( ) для конвертування в мережевий порядок байтів, то в результаті старший байт (0x1) виявиться за меншим адресою. Природно, що якщо роздрукувати значення змінної x після зміни її порядку байтів, то вийде безглузде число.


Приклад 9 показує результати роботи тієї ж програми на процесорі з режимом “від старшого до молодшого”.


Приклад 9. Результат роботи програми на процесорі з режимом “від старшого до молодшого”





x in hex: 112a380
x by bytes: 1 12 a3 80
After htonl()
x in hex: 112a380
x by bytes: 1 12 a3 80

Тут видно, що найстарший байт (0x1) записаний під меншим адресою. Виклик htonl() для конвертування в мережевий порядок не змінить нічого тому, що порядок байтів “від старшого до молодшого”.


Зміна на зворотний порядку байтів


Тепер напишемо програму, результати роботи якої не залежать від порядку байтів. Необхідно переконатися, що дані у файлі записані у вірному порядку під час запису в цей файл або читанні з нього даних. Також слід уникати використання прапорів умовної компіляції і передбачити в коді програми можливістю автоматичного визначення порядок байтів, використовуваний на конкретному комп’ютері.


Створимо набір функцій, які автоматично змінює на зворотний порядок байтів заданого параметра в залежності від порядку байтів на комп’ютері.


Спочатку потрібно розібратися з параметром s типу short, Розділивши його два байта, а потім “склеїти” їх у зворотному порядку. Як показано в прикладі 10 нижче, функція поверне реверсувати значення змінної типу short у випадку, якщо процесор використовує порядок байтів “від молодшого до старшого”. В іншому випадку функція залишить колишнім значення змінної s.


Приклад 10. Метод 1: Використання побітового зсуву і склеювання бітів





short reverseShort (short s) {
unsigned char c1, c2;
if (is_bigendian()) {
return s;
} else {
c1 = s & 255;
c2 = (s >> 8) & 255;
return (c1 << 8) + c2;
}
}

У функції нижче перетвориться переменнаяю типу short, Щоб представити її як масив символів, а потім створюється новий масив, який є зворотним першому в разі, якщо процесор використовує режим “від молодшого до старшого”.


Приклад 11. Метод 2: Використання покажчиків на символьний масив





short reverseShort (char *c) {
short s;
char *p = (char *)&s;
if (is_bigendian()) {
p[0] = c[0];
p[1] = c[1];
} else {
p[0] = c[1];
p[1] = c[0];
}
return s;
}

Тепер перейдемо до типу int.


Приклад 12. Метод 1: Використання побітового зсуву і склеювання байтів для типу int





int reverseInt (int i) {
unsigned char c1, c2, c3, c4;
if (is_bigendian()) {
return i;
} else {
c1 = i & 255;
c2 = (i >> 8) & 255;
c3 = (i >> 16) & 255;
c4 = (i >> 24) & 255;
return ((int)c1 << 24) + ((int)c2 << 16) + ((int)c3 << 8) + c4;
}

Це в більшій чи меншій мірі відповідає тому, що ми раніше робили для зміни на зворотний порядку для типу short, Але для чотирьох байтів, а не двох.


Приклад 13. Метод 2: Використання покажчиків на символьний масив для типу int





short reverseInt (char *c) {
int i;
char *p = (char *)&i;
if (is_bigendian()) {
p[0] = c[0];
p[1] = c[1];
p[2] = c[2];
p[3] = c[3];
} else {
p[0] = c[3];
p[1] = c[2];
p[2] = c[1];
p[3] = c[0];
}
return i;
}

Те ж саме ми робили для реверсування змінної типу short, Але тут оброблялися чотири байти.


Точно також можна міняти на зворотний порядок байтів для типів float, long, double та інших типів, але розгляд цих питань виходить за рамки цієї статті.


Висновок


За великим рахунком, немає особливої ​​переваги у використанні того чи іншого порядку байтів. Обидва порядку поширені і використовуються в архітектурі багатьох ЕОМ. Процесори, що працюють в режимі “від молодшого до старшого “використовуються на більшості персональних комп’ютерів.


Проблеми з порядком байтів не зачіпають змінні, що зберігаються тільки в одному байті, оскільки “байт” вважається елементарною одиницею зберігання інформації в пам’яті. З іншого боку багатобайтові змінні залежать від порядку байтів, що слід мати на увазі при створенні програми.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*