C語言可否自定義數值類型(或是任意個位元組的數值類型)?

不只是char short int long..

例如3個位元組的數值類型(取值範圍為0~0xFFFFFF,存儲長度3個位元組)

或是兩個12bit的連續放置的數值類型(單個取值範圍為0~0xFFF,存儲總長度3個位元組)


可以用 Bit fields,但單獨使用時要做 packing:

#include &

#pragma pack(push)
#pragma pack(1)

typedef struct {
unsigned u : 24;
}Foo;

typedef struct {
unsigned x : 12;
unsigned y : 12;
}Bar;

#pragma pack(pop)

int main() {
printf("sizeof(Foo) = %u
", (unsigned)sizeof(Foo));

Foo a = { 123 }, b = { 321 };
Foo c = { a.u + b.u };
printf("c = %u
", c.u);

Foo d = { 0xFFFFFF};
Foo e = { 0x1000000 }; // warning: large integer implicitly truncated to unsigned type [-Woverflow]
printf("d = %u
", d.u);
printf("e = %u
", e.u);

printf("sizeof(Bar) = %u
", (unsigned)sizeof(Bar));
Bar z = { 10, 20 };
printf("z.x + z.y = %d
", z.x + z.y);
}

輸出:

sizeof(Foo) = 3
c = 444
d = 16777215
e = 0
sizeof(Bar) = 3
z.x + z.y = 30

.


不能。

C自身的類型系統沒有這樣的功能,而其提供的封裝性也不足以讓用戶自定義出這樣的類型,只能很彆扭地實現題主的需求。

寫一大堆宏或者函數的彆扭實現方式肯定不是題主真的想要的效果,所以說不能。

========================================================

題主想要的東西是類似Ada里的constrained subtype,例如:

type UInt24 is range 0 .. 16#FFFFFF#

或顯式指定T"Size屬性:

type UInt24 is range 0 .. 16#FFFFFF#; for UInt24"Size use 24;

這樣得到的UInt24類型的值域就是[0, 0xFFFFFF]了,而且所有原本在Integer上的運算符(+、-、*、/之類)都可以使用。

編譯器會知道這個類型的值的有效範圍只需要3位元組來存儲,但是一個單獨的UInt24值可能實際使用更多空間來存儲(例如下面例子I用了4位元組),這是編譯器的自由;在packed array / record里則可以保證它只使用3位元組存儲(例如下面UInt24Array8會用24位元組),也就滿足了題主的主要需求。

with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is
type UInt24 is range 0 .. 16#FFFFFF#;
type UInt24Array8 is array (1 .. 8) of UInt24;
pragma Pack(UInt24Array8);
I : constant UInt24 := 123;
begin
Put_Line(Item =&> "Size of UInt24: " Integer"Image(UInt24"Size)); -- 24
Put_Line(Item =&> "Size of I: " Integer"Image(I"Size)); -- 32
Put_Line(Item =&> "Size of UInt24Array8: " Integer"Image(UInt24Array8"Size)); -- 192 == 24*8
end Hello;

兩個12bit的例子同理:

with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is
type UInt12 is range 0 .. 16#FFF#;
type UInt12Array2 is array (1 .. 2) of UInt12;
pragma Pack(UInt12Array2);
begin
Put_Line(Item =&> "Size of UInt24: " Integer"Image(UInt12"Size)); -- 12
Put_Line(Item =&> "Size of UInt12Array2: " Integer"Image(UInt12Array2"Size)); -- 24 == 12*2
end Hello;

========================================================

@Milo Yip 的答案用bit field,是C里最接近題主想要的功能的做法。基礎代碼可以參考他的回答。

通常可以用一個「封裝」用的宏來讓代碼看起來更正常,例如:

#include &

#pragma pack(push)
#pragma pack(1)

typedef struct {
unsigned int u : 24;
} uint24_t;

#pragma pack(pop)

#define val(x) (x).u

void foo() {
uint24_t i, j;
val(i) = 127;
val(j) = 42;
val(i) = val(i) + val(j);
printf("sizeof(uint24_t) = %lu
",
sizeof(uint24_t)); // 3
printf("i = %d
", val(i)); // 169
}

Clang 3.5.0在x86-64在-O2下會生成:

foo(): # @foo()
push rax
mov edi, .L.str
mov esi, 3
xor eax, eax
call printf
mov edi, .L.str1
mov esi, 169
xor eax, eax
pop rdx
jmp printf # TAILCALL

.L.str:
.asciz "sizeof(uint24_t) = %lu
"

.L.str1:
.asciz "i = %d
"

可見變數i最後的值被分配到了esi上(mov esi, 169),而esi還是4位元組的——當然,x86-64沒有3位元組的寄存器嘛。

========================================================

如果要像 C語言可否自定義數值類型(或是任意個位元組的數值類型)? - 匿名用戶的回答 那樣自定義存儲:

struct ThreeBytes
{
char first;
char second;
char third;
};

那在C里這個ThreeBytes類型就無法使用int原本可以用的內建運算符。它的look and feel就會跟普通的整型差許多。

如果是C++的話,好歹還可以通過類與運算符重載來把運算符加回來,但C沒這福利。


已經有人回答了 Bit fields ,在這裡我補充一下,使用 Bit fields 要注意位元組序的問題。

How Endianness Effects Bitfield Packing

上面鏈接比較詳細的說明了位元組序對 Bit fields 的影響 。

下面的鏈接則給出了一些檢測位元組序的技巧。

Pre-defined Compiler Macros / Wiki / Endianness


struct {

int nVal1 : 1; // 改變成員佔位

int : 1; // 間距無法直接使用

int : 0; // 間距可以為0位元組,但有名稱的欄位無法設置為0位元組

} myStruct;


@Milo Yip 大神,我照抄您的代碼運行,但結果與期待的不同,請問這是機器的原因嗎?

問一句,那個【unsigned u : 24】中,那個【 : 】號的作用是什麼?附言:那個112和211是我為了排除巧合改了一下,第一次的123和321也是和您機器的運行效果不同的。


當然可以,不過實現起來麻煩一些而已。

你完全可以用多個 char 拼成一個數據結構,比如三位元組數值。

struct ThreeBytes
{
char first;
char second;
char third;
}


推薦閱讀:

利用異或方式交換兩個變數值的原理是什麼?
為什麼計算機里加上存儲功能可以代替改變電路?
為什麼開源軟體絕大部分都是C語言寫的,而商業軟體大多數是C++開發的?
成為一個優秀的程序員,一定要精通C/C++嗎?
如今存在用機器語言編寫出的程序么?

TAG:程序員 | C編程語言 |