[英]How to divide an Int into two Bytes in C?
我正在使用嵌入在最小硬件中的軟件,該軟件僅支持ANSI C並且具有最小版本的標准IO庫。
我有一個Int變量,大小為兩個字節,但我需要分別將它分成2個字節以便能夠傳輸它,然后我可以讀取這兩個字節,重組原始的Int。
我可以想到每個字節的二進制除法如下:
int valor = 522; // 0000 0010 0000 1010 (entero de 2 bytes)
byte superior = byteSuperior(valor); // 0000 0010
byte inferior = byteInferioror(valor); // 0000 1010
...
int valorRestaurado = bytesToInteger(superior, inferior); // 522
但是我沒有以一種簡單的方式將整體按其重量分開,這讓我覺得它應該是微不足道的(例如用位移)而我卻沒有發現它。
實際上,任何將整體划分為2個字節並重新組合的解決方案對我來說都很有用。
從非常感謝你!
這不是一個“簡單”的任務。
首先,C中一個字節的數據類型是char
。 你可能想要unsigned char
,因為char
可以是signed或unsigned,它是實現定義的。
int
是一個帶符號的類型,它也可以右移它實現定義。 就C而言, int
必須至少有 16位(如果char
有8位,則為2個字節),但可以有更多。 但是,正如您的問題所寫,您已經知道平台上的int
有16位。 在您的實現中使用這些知識意味着您的代碼特定於該平台而不是可移植的。
在我看來,你有兩個選擇:
您可以使用屏蔽和位移來處理int
的值,例如:
int foo = 42; unsigned char lsb = (unsigned)foo & 0xff; // mask the lower 8 bits unsigned char msb = (unsigned)foo >> 8; // shift the higher 8 bits
這樣做的好處是,您可以獨立於內存中int
的布局。 對於重建,請執行以下操作:
int rec = (int)(((unsigned)msb << 8) | lsb );
注意在這里將msb
為unsigned
是必要的,否則,它將被提升為int
( int
可以表示unsigned char
所有值),當移位8個位置時可能會溢出。 正如你已經說過你的int
有“兩個字節”,這在你的情況下非常可能。
對int
的最終強制轉換也是實現定義的 ,但是如果編譯器不做“奇怪”的事情,它將在你的“典型”平台上工作,並且在2的補碼中使用16位int
。 通過首先檢查unsigned
是否對於int
來說太大(因為原始int
是負的),你可以避免這種情況,例如
unsigned tmp = ((unsigned)msb << 8 ) | lsb; int rec; if (tmp > INT_MAX) { tmp = ~tmp + 1; // 2's complement if (tmp > INT_MAX) { // only possible when implementation uses 2's complement // representation, and then only for INT_MIN rec = INT_MIN; } else { rec = tmp; rec = -rec; } } else { rec = tmp; }
2的補碼在這里很好,因為在C標准中明確規定了將負int
轉換為unsigned
的規則。
您可以在內存中使用表示,例如:
int foo = 42; unsigned char *rep = (unsigned char *)&foo; unsigned char first = rep[0]; unsigned char second = rep[1];
但要注意, first
是MSB還是LSB取決於您機器上使用的字節順序 。 此外, 如果你的int
包含填充位 (在實踐中極不可能,但C標准允許),你也會讀它們。 對於重建,請執行以下操作:
int rec; unsigned char *recrep = (unsigned char *)&rec; recrep[0] = first; recrep[1] = second;
從目前為止的幾個答案可以看出,有多種方法,有些可能是令人驚訝的微妙之處。
“數學”方法。 您使用移位和屏蔽(或等效地,除法和余數)來分隔字節,並類似地重新組合它們。 這是Felix Palmen的答案中的 “選項1”。 這種方法的優點是它完全獨立於“字節序”問題。 它的復雜性在於它受到一些符號擴展和實現定義問題的影響。 如果對復合int
和等式的字節分隔部分使用unsigned
類型,則最安全。 如果使用帶符號類型,則通常需要額外的強制轉換和/或掩碼。 (但話雖如此,這是我更喜歡的方法。)
“記憶”的方法。 您可以使用指針或union
來直接訪問構成int
的字節。 這是Felix Palmen的答案中的“選項2”。 這里非常重要的問題是字節順序或“字節順序”。 此外,根據您的實現方式,您可能會違反“嚴格別名”規則 。
如果使用“數學”方法,請確保在值設置上執行和不設置高位的值進行測試。 例如,對於16位,一套完整的測試可能包括值0x0101
, 0x0180
, 0x8001
和0x8080
。 如果你沒有正確編寫代碼(如果你使用有符號類型實現它,或者你省略了一些其他必要的掩碼),你通常會發現額外的0xff
進入重構結果,破壞了傳輸。 (另外,您可能需要考慮編寫正式的單元測試 ,以便最大限度地提高代碼重新測試的可能性,以及檢測到的任何潛在錯誤,如果/何時將其移植到執行不同實現選擇的計算機上哪個會影響它。)
如果您確實希望傳輸有符號值,則會出現一些額外的復雜情況。 特別是,如果在類型為int
大於16位的機器上重建16位整數,則可能必須顯式簽名對其進行擴展以保留其值。 同樣,全面測試應該確保您已經充分解決了這些復雜問題(至少在目前為止測試代碼的平台上:-))。
讓我們再回到測試值,我建議( 0x0101
, 0x0180
, 0x8001
和0x8080
),如果您傳輸的無符號整數,這對應於257,384,32769和32896.如果你傳送符號整數,它們對應於257,384,-32767和-32640。 如果在另一端你得到像-693或65281(對應於十六進制0xff01
)的值,或者當你預期-32640得到32896時,它表示你需要返回並更加小心你的簽名/未簽名用法,屏蔽和/或明確的符號擴展名。
最后,如果您使用“內存”方法,並且您的發送和接收代碼在不同字節順序的計算機上運行,您將找到交換的字節。 0x0102
將變為0x0201
。 有多種方法可以解決這個問題,但這可能會非常麻煩。 (這就是為什么,正如我所說,我通常更喜歡“數學”方法,所以我可以回避字節順序問題。)
假設int
是兩個字節,並且每個字節的位數( CHAR_BIT
)是8,並且使用了兩個補碼,則可以將名為valor
的int
拆解為與endian無關的順序:
unsigned x;
memcpy(&x, &valor, sizeof x);
unsigned char Byte0 = x & 0xff;
unsigned char Byte1 = x >> 8;
並且可以從unsigned char Byte0
和unsigned char Byte1
重新組合:
unsigned x;
x = (unsigned) Byte1 << 8 | Byte0;
memcpy(&valor, &x, sizeof valor);
筆記:
int
和unsigned
具有相同的大小和對齊符合C 2011(N1570)6.2.5 6。 unsigned
填充位,因為C要求UINT_MAX
至少為65535,因此值表示需要所有16位。 int
和unsigned
具有相同的字節順序6.2.6.2 2。 實際上,您可以將整數變量的地址強制轉換為字符指針( unsigned char*
,准確),讀取該值然后將指針遞增以指向下一個字節以再次讀取該值。 這符合別名規則。
我甚至不會寫函數來做這件事。 這兩個操作都是C的按位運算符的簡單應用:
int valor = 522;
unsigned char superior = (valor >> 8) & 0xff;
unsigned char inferior = valor & 0xff;
int valorRestaurado = (superior << 8) | inferior;
雖然它看起來很簡單,但在編寫這樣的代碼時總會有一些細微之處,並且很容易弄錯。 例如,由於valor
已簽名,因此使用>>
將其右移是實現定義的,盡管通常意味着它可能簽署擴展或不簽名,這最終不會影響& 0xff
選擇的字節的值,指派superior
。
此外,如果將superior
或inferior
定義為帶符號類型,則在重建期間可能存在問題。 如果它們小於int
(當然它們必然是),它們將在重建的其余部分發生之前立即進行符號擴展到int
,從而破壞結果。 (這就是為什么我在我的例子中明確地聲明superior
和inferior
為unsigned char
類型。如果你的byte
類型是unsigned char
的typedef,那也沒關系。)
還有一個不起眼的溢出可能性潛伏在子表達式superior << 8
,即使superior
是無符號的,但也未必在實踐中產生問題。 (有關其他說明,請參閱Eric Postpischil的評論。)
簡單地定義一個聯合:
typedef union
{
int as_int;
unsigned char as_byte[2];
} INT2BYTE;
INT2BYTE i2b;
將整數值放在i2b.as_int
成員中,並從i2b.as_byte[0]
和i2b.as_byte[1]
獲取等效字節。
我使用int shrot而不是int來dry
,因為在PC上int是4個字節,在我的目標平台上它們是2.使用unsigned使調試更容易。
代碼用GCC編譯(並且應該與幾乎任何其他C編譯器一起使用)。 如果我沒有錯,那取決於架構是big endian
還是little endian
,但它可以通過反轉重構整數的線來解決:
#include <stdio.h>
void main(){
// unsigned short int = 2 bytes in a 32 bit pc
unsigned short int valor;
unsigned short int reassembled;
unsigned char data0 = 0;
unsigned char data1 = 0;
printf("An integer is %d bytes\n", sizeof(valor));
printf("Enter a number: \n");
scanf("%d",&valor);
// Decomposes the int in 2 bytes
data0 = (char) 0x00FF & valor;
data1 = (char) 0x00FF & (valor >> 8);
// Just a bit of 'feedback'
printf("Integer: %d \n", valor);
printf("Hexa: %X \n", valor);
printf("Byte 0: %d - %X \n", data0, data0);
printf("Byte 1: %d - %X \n", data1, data1);
// Reassembles the int from 2 bytes
reassembled = (unsigned short int) (data1 << 8 | data0);
// Show the rebuilt number
printf("Reassembled Integer: %d \n", reassembled);
printf("Reassembled Hexa: %X \n", reassembled);
return;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.