最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++中整數(shù)類型(Integer?Types)的避雷指南與正確使用姿勢詳解

 更新時間:2026年02月25日 08:50:37   作者:PAK向日葵  
在實際開發(fā)中,如果對C++內(nèi)建整數(shù)類型的機(jī)制不熟悉,或者不遵循一定的使用規(guī)范,則非常容易引入難以排查和調(diào)試的Bug,下面小編就和大家詳細(xì)講解一下C++中整數(shù)類型的正確使用姿勢吧

背景

C++繼承自C語言。作為一門以零開銷抽象為主要特征的底層語言,不同于Python或JavaScript等高抽象層次的語言,C++擁有一套較為完整、但又包含有一定歷史包袱的內(nèi)建整數(shù)類型。

在實際開發(fā)中,如果對C++內(nèi)建整數(shù)類型的機(jī)制不熟悉,或者不遵循一定的使用規(guī)范,則非常容易引入難以排查和調(diào)試的Bug。因此學(xué)習(xí)了解C++中內(nèi)建整數(shù)類型的特性,以及一套行之有效的使用規(guī)范,是非常有必要的。

內(nèi)建整數(shù)類型的坑 or 歷史包袱

C++ 標(biāo)準(zhǔn)沒有規(guī)定具體位數(shù)

雖然在實際實踐中,我們知道在x64平臺,對絕大多數(shù)編譯器來說:

  • short => 16 bit
  • int => 32 bit
  • long => 32 bit(Windows)或64 bit(Linux)
  • long long => 64 bit

但坑爹的地方在于,C++ 標(biāo)準(zhǔn)沒有規(guī)定 int、long 等類型的具體位數(shù)。

C++ 標(biāo)準(zhǔn)只規(guī)定了最小寬度(比如int的最小寬度是16 bit)和相對大小(比如sizeof(short) <= sizeof(int) <= sizeof(long))。

這意味著如果我們要追求代碼的嚴(yán)謹(jǐn)性和在未來的可移植性,就不能在使用時假定這些內(nèi)建類型的具體位數(shù)。

坑爹的 unsigned 類型

人類直覺認(rèn)為“大小、長度、年齡等不可能為負(fù)數(shù)”,所以很自然地在這種場景下傾向于用 unsigned。但 C++ 規(guī)定,無符號整數(shù)的溢出是合法的模運算(Modulo Arithmetic)。

這意味著,無符號數(shù)永遠(yuǎn)不可能為負(fù),當(dāng)它為 0 時再減 1,不會變成 -1,而是會回繞成該類型的最大值(如 32 位下變成 2³² - 1,即 4294967295)。

一旦掉進(jìn)這個坑里,會導(dǎo)致以下幾種致命 Bug:

死循環(huán)

// 災(zāi)難:如果用 unsigned 表示數(shù)組下標(biāo),執(zhí)行倒序遍歷
for (unsigned int i = vec.size() - 1; i >= 0; --i) { 
    // i 為 0 時,--i 變成 4294967295,依然 >= 0。
    // 死循環(huán) ?。?!
}

差值計算災(zāi)難

usigned int a = 2020;
usigned int b = 2026;
if (a - b < 0) {
  std::cout << "a < b" << std::endl;
} else {
  std::cout << "a >= b" << std::endl;
}

結(jié)果會輸出a >= b,因為a-b的結(jié)果是一個極大的正數(shù),導(dǎo)致邏輯判斷完全相反。

坑爹的隱式類型提升

混合運算引發(fā)的類型提升

C++ 為了讓不同類型的數(shù)字能在一起做數(shù)學(xué)運算,制定了一套極其復(fù)雜的整型提升規(guī)則(Integer Promotion Rules) 。最反直覺的一條是:當(dāng)有符號數(shù)和無符號數(shù)混合運算時,有符號數(shù)會被隱式強(qiáng)制轉(zhuǎn)換為無符號數(shù)

這會引發(fā)類似下面的Bug:

int a = -1;
unsigned int b = 1;

if (a < b) {
    // 你以為會執(zhí)行這里?錯!
} else {
    // 實際會執(zhí)行這里!
    // 因為 a 被偷偷轉(zhuǎn)換成了 unsigned,-1 變成了 4294967295
    // 4294967295 < 1 顯然是 false。
}

這種錯誤如果出現(xiàn)在緩沖區(qū)檢查、長度驗證、邊界判斷等敏感地帶,就非常容易被攻擊者設(shè)計繞過檢查,從而引發(fā)更嚴(yán)重的安全問題。

算術(shù)運算引發(fā)的類型提升

例子:

  uint8_t a = 254;
mov   byte ptr [a],0FEh  
  uint8_t b = 255;
mov   byte ptr [b],0FFh  
  uint8_t c = a + b;
movzx eax,byte ptr [a]  // 隱式提升a為uint32_t
movzx ecx,byte ptr [b]  // 隱式提升b為uint32_t
add   eax,ecx // 計算(uint32_t)a+(uint32_t)b
mov   byte ptr [c],al // 將eax當(dāng)中的計算結(jié)果強(qiáng)行截斷成8bit,然后寫回c變量

分析匯編代碼,可知計算a+b時,ab中的值會被隱式提升成32bit。

盡管如此,但寫回計算結(jié)果時仍然會發(fā)生截斷。c中的計算結(jié)果仍然是錯誤的。

有符號溢出 = 未定義行為(UB)

不同于剛才聊的無符號類型溢出會引發(fā)"回繞"現(xiàn)象,在C++中,有符號整型的溢出被視作一種UB行為。

舉個例子:

int x = std::numeric_limit<int>::max();
x += 1;  // UB

理論上編譯器可能會直接決定將這行UB代碼優(yōu)化掉,或者引發(fā)其他異?,F(xiàn)象。

一個更極端的例子:

int f(int x) {  
  if (x + 1 > x)  
    return 1;  
  else  
    return 0;  
}

在高優(yōu)化編譯模式(比如release)下,編譯器可能會認(rèn)為:既然 signed 溢出是 UB,那么我直接忽略處理 UB 的情況,即假設(shè) x+1 一定不溢出。因此我直接將f優(yōu)化成永遠(yuǎn)return 1。

那么此時你調(diào)用f(std::numeric_limits<int>::max())就會得到錯誤的結(jié)果。

在實踐中,如果編譯器優(yōu)化掉的恰好是重要的安全檢查,那么就可能引發(fā)更嚴(yán)重的安全漏洞。

坑爹的靜默截斷

大類型 → 小類型 => 靜默截斷

比如:

int64_t n = 5000000000;
int x = n;
int limit = 800000000;
if (x < limit) {
  std::cout << x << std::endl;  // 得到垃圾值705032704
}

在你的編譯器沒有經(jīng)過特殊設(shè)置的情況下,以上代碼會通過編譯。并且盡管n遠(yuǎn)大于limit,if塊中的代碼仍然被執(zhí)行了。

在實踐中,如果x被用于表示文件大小、網(wǎng)絡(luò)長度或用戶輸入長度,那么攻擊者可以通過構(gòu)造一個超大數(shù)字n并依靠靜默截斷來繞過檢查if (x < limit)

標(biāo)準(zhǔn)庫的世紀(jì)失誤

早期 C++ 標(biāo)準(zhǔn)委員會為了讓容器(如 std::vector)能容納盡可能多的元素,利用了無符號數(shù)比有符號數(shù)正向范圍大一倍的特點,將容器的 size() 返回值和 operator[] 的參數(shù)硬性規(guī)定為 size_t(一個無符號類型)。

坑爹的地方就在這兒,由于size_t是一個無符號類型,因此你一旦調(diào)用STL庫容器的size(),就必須警惕掉進(jìn)前述的任何與無符號整型有關(guān)的坑。

為了讓你加深印象,這里再強(qiáng)調(diào)一遍。

逆向迭代陷阱(Underflow)

for (size_t i = v.size() - 1; i >= 0; --i) { // 永遠(yuǎn)不會停止!
    // 當(dāng) i 為 0 時,--i 會變成一個巨大的正數(shù)(溢出/繞回)
}

隱式類型轉(zhuǎn)換與比較錯誤

std::vector<int> v;
int x = -1;
if (x < v.size()) { 
    // 如果 v 為空(size 為 0),這個條件居然是 FALSE!
    // 因為 -1 被轉(zhuǎn)換成了 18446744073709551615 (2^64-1)
}

C++ 之父 Bjarne Stroustrup 和多位委員會成員后來公開承認(rèn):這是一個巨大的錯誤(A Historical Mistake)。但為了 ABI 兼容,永遠(yuǎn)無法修改了。

Google C++ Style Guide 規(guī)范是怎么說的

為了避雷前述的C++內(nèi)建整型類型的各種坑或歷史包袱,Google 制定了一系列可實操的工程規(guī)范。

下面我對這部分規(guī)范進(jìn)行了梳理和拓展。平時開發(fā)中遵循這些規(guī)范,就能避免掉一大部分的坑~

推薦使用<stdint.h>或<cstdint>的固定寬度類型

既然short、long longunsigned long long等類型的位寬是不確定的,那干脆我們就不要去用了。

取而代之,我們使用固定寬度類型,比如int16_t、uint32_tint64_t。

注意,這些類型直接使用即可,不必加std::前綴!

int類型的正確使用姿勢

1.在 C++ 內(nèi)置整數(shù)類型中,唯一推薦經(jīng)常使用的是 int。比如在數(shù)據(jù)范圍適用的前提下,在以下場景:

  • 循環(huán)計數(shù)器
  • 一般的小整數(shù)
  • 下標(biāo)

2.如果一個值可能 ≥ 2³¹(約 21 億),就應(yīng)使用 64 位類型(int64_t)。

特別的,如果某個值/變量本身不大,但在中間計算過程中可能溢出,也應(yīng)當(dāng)使用int64_t

3.如果程序明確需要特定大小的整數(shù)類型,應(yīng)使用int16_tint32_t、int64_t精確寬度類型。

比如,TCP協(xié)議規(guī)范中端口字段明確為32bit,那么你就應(yīng)該明確地用int32_t而不是int。

強(qiáng)烈抵制無符號(unsigned)類型

原則

既然混用signed和unsigned容易翻車(比如剛才提到的隱式類型提升),Google 的做法非常簡單粗暴——絕大多數(shù)業(yè)務(wù)代碼里直接禁用 unsigned,全部用有符號整型。這直接消滅了混用的可能性。

特別強(qiáng)調(diào),不要為了“保證非負(fù)”而用 unsigned。

錯誤示范:

unsigned int age; // ? 只是想讓 age >= 0

正確做法:

int age;
// 如果你想保證它不能為負(fù)數(shù),在代碼里寫斷言。
assert(age >= 0);

豁免

只有當(dāng)你明確在以下場景時,才能使用無符號類型:

  • 需要進(jìn)行位操作(如移位、按位邏輯操作)
    • 對于有符號整數(shù)(如 int),進(jìn)行右移操作(>>)時,到底是“邏輯右移(補(bǔ)0)”還是“算術(shù)右移(補(bǔ)符號位)”在 C++20 以前不確定的(通常是算術(shù)右移,會補(bǔ)符號位)
    • 這會帶來跨平臺的不確定性。
    • 而無符號類型進(jìn)行位運算(&, |, ^, <<, >>)有著絕對一致的跨平臺表現(xiàn)。
  • 表示位掩碼(bitmask)或位域(bitfields)
  • 需要利用無符號類型的溢出回繞特性(比如密碼學(xué)或哈希算法)
  • 用于表示單個二進(jìn)制字節(jié)的值
    • 當(dāng)我們在進(jìn)行網(wǎng)絡(luò)編程、文件 IO、序列化時,處理的基礎(chǔ)單位是“字節(jié)”。一個字節(jié)就是 8 個比特,它沒有正負(fù)之分。
    • 如果你用 char(有符號),當(dāng)讀取到大于 127 的字節(jié)時,它會被解釋為負(fù)數(shù),這在作為數(shù)組索引或進(jìn)行寬類型轉(zhuǎn)換時會引發(fā)嚴(yán)重的 Bug。

例子1:

LevelDB 使用了自定義的 MurmurHash 變種。你看這里清一色使用的是 uint32_t

// 來源:google/leveldb
// 這里的 seed, m, r 以及 h 都在進(jìn)行位操作和故意的溢出計算
uint32_t Hash(const char* data, size_t n, uint32_t seed) {
  // 常量 m 充當(dāng)乘法因子,利用無符號乘法溢出截斷的特性
  const uint32_t m = 0xc6a4a793;
  const uint32_t r = 24;
  const char* limit = data + n;
  
  // seed 和 (n * m) 進(jìn)行異或,n*m 極可能溢出,但 uint32_t 保證了其安全性
  uint32_t h = seed ^ (n * m);

  // 一段典型的每次處理 4 字節(jié)的哈?;旌线^程
  while (data + 4 <= limit) {
    uint32_t w = DecodeFixed32(data); // 讀取 4 個原始字節(jié)
    data += 4;
    h += w;           // 這里的加法依賴模 2^32 運算
    h *= m;           // 乘法依賴模 2^32 運算
    h ^= (h >> 16);   // 位移與異或,打亂比特位
  }
  // ...
  return h;
}

例子2:

在 Protobuf 的底層序列化格式中,一個字段的標(biāo)簽(Tag)由“字段編號(Field Number)”和“數(shù)據(jù)類型(Wire Type)”壓縮進(jìn)同一個整數(shù)中。

// 來源:google/protobuf
// 使用無符號整數(shù)來進(jìn)行左移、按位或、按位與等操作
inline uint32_t WireFormatLite::MakeTag(int field_number, WireType type) {
  // 將 field_number 左移 3 位,然后與低 3 位的 type 進(jìn)行按位或 (|)
  // uint32_t 保證了移位操作絕對不會受符號位影響
  return (static_cast<uint32_t>(field_number) << 3) | static_cast<uint32_t>(type);
}

inline WireFormatLite::WireType WireFormatLite::GetTagWireType(uint32_t tag) {
  // 使用按位與 (&) 提取低 3 位的數(shù)據(jù)
  return static_cast<WireType>(tag & 7);
}

例子3:

Base64 編碼解碼時,針對的是原始字節(jié)流。

// 來源:google/abseil (absl)
// 使用 uint8_t 數(shù)組來表示原始的字節(jié)流序列
static const uint8_t kBase64DecoderRules[256] = {
    // ... 大量解析規(guī)則狀態(tài)碼 ...
};

bool Base64UnescapeInternal(const char* src_param, size_t szsrc,
                            char* dest, size_t* szdest) {
  // 將輸入的字符指針強(qiáng)制轉(zhuǎn)換為無符號的字節(jié)流指針
  // 因為 Base64 處理過程中,我們只關(guān)心這 8 個 bit 是什么,不關(guān)心它代表什么字符或正負(fù)數(shù)
  const uint8_t* src = reinterpret_cast<const uint8_t*>(src_param);
  const uint8_t* src_end = src + szsrc;
  
  while (src < src_end) {
    // 作為數(shù)組索引時,如果是 signed char 遇到大于 127 的值會變成負(fù)數(shù)而越界崩潰
    // uint8_t 完美避免了這個問題
    uint8_t rule = kBase64DecoderRules[*src++];
    // ...
  }
}

size_t正確使用姿勢

雖然前文中我們細(xì)數(shù)了size_t作為無符號整型的一系列罪狀,但由于其較為明確的語義(比如用于表示內(nèi)存數(shù)據(jù)塊的字節(jié)數(shù)、偏移量),Google規(guī)范中也沒有一棍子打死它,而是允許在適當(dāng)?shù)那闆r下使用。

原文:When appropriate, you are welcome to use standard type aliases like size_t and ptrdiff_t.

舉例:TensorFlow 代碼中 size_t 與 int64_t 并存

來源于 TensorFlow 官方 C++ API 文檔示例:

size_t TotalBytes() const   // returns memory usage
int64_t dim_size(int d) const   // returns shape dimension
  • TotalBytes() 用的是 size_t,很自然用于表示內(nèi)存 尺寸/字節(jié)數(shù)(不可能為負(fù))。
  • dim_size() 返回 int64_t,用于表示 tensor 的 邏輯維度大小/形狀,因為:
    • TensorFlow 的維度整數(shù)可能參與算術(shù)計算
    • 需要 signed 類型有助于防止 signed/unsigned 隱式轉(zhuǎn)換問題

容器大小要謹(jǐn)慎

針對表示STL容器大小的size_t存在的缺陷,Google建議:盡量使用迭代器(iterators)和容器(containers),而不是指針(pointers)和大?。╯izes)

// ? Good
for (auto it = v.begin(); it != v.end(); ++it);

// ? Good
for (auto& x : v);

// ? Bad(混用signed和unsigned)
for (int i = 0; i < v.size(); i++);

另外,需要盡量避免無意義的 unsigned 擴(kuò)散到業(yè)務(wù)代碼。

以下是一個 Good case:

size_t size = container.size();             // 與 STL 兼容
int64_t count = static_cast<int64_t>(size); // 內(nèi)部轉(zhuǎn)換防止 signed/unsigned 混用
for (int64_t i = 0; i < count; ++i) { ... } // 內(nèi)部循環(huán)

這種方式既兼容了容器接口的 size_t,又避免了 signed/unsigned 混用引發(fā)的 bug。

以上就是C++中整數(shù)類型(Integer Types)的避雷指南與正確使用姿勢詳解的詳細(xì)內(nèi)容,更多關(guān)于C++整數(shù)類型的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C++之try catch 異常處理入門實例

    C++之try catch 異常處理入門實例

    我們通常希望自己編寫的程序能夠在異常的情況下也能作出相應(yīng)的處理,而不至于程序莫名其妙地中斷或者中止運行了。在設(shè)計程序時應(yīng)充分考慮各種異常情況,并加以處理
    2018-01-01
  • vs code 配置python虛擬環(huán)境的方法

    vs code 配置python虛擬環(huán)境的方法

    這篇文章主要介紹了vs code 配置python虛擬環(huán)境的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • C語言堆結(jié)構(gòu)處理TopK問題詳解

    C語言堆結(jié)構(gòu)處理TopK問題詳解

    TopK問題即在N個數(shù)中找出最大的前K個,這篇文章將詳細(xì)講解如何利用小根堆的方法解決TopK問題,文中代碼具有一定參考價值,快跟隨小編一起學(xué)習(xí)一下吧
    2022-06-06
  • OpenCV使用GrabCut實現(xiàn)摳圖功能

    OpenCV使用GrabCut實現(xiàn)摳圖功能

    Grabcut是基于圖割(graph cut)實現(xiàn)的圖像分割算法,它需要用戶輸入一個bounding box作為分割目標(biāo)位置,實現(xiàn)對目標(biāo)與背景的分離/分割。本文將使用GrabCut實現(xiàn)摳圖功能,需要的可以參考一下
    2023-02-02
  • uboot添加自定義命令的實現(xiàn)步驟

    uboot添加自定義命令的實現(xiàn)步驟

    uboot 屬于bootloader的一種,是用來引導(dǎo)啟動內(nèi)核的,它的最終目的就是從flash中讀出內(nèi)核,放到內(nèi)存中,啟動內(nèi)核,這篇文章主要介紹了uboot添加自定義命令的實現(xiàn)步驟,需要的朋友可以參考下
    2022-11-11
  • C語言版簡單掃雷游戲

    C語言版簡單掃雷游戲

    這篇文章主要為大家詳細(xì)介紹了C語言版簡單掃雷游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • C++輸出問題:保留兩位小數(shù)

    C++輸出問題:保留兩位小數(shù)

    這篇文章主要介紹了C++輸出問題:保留兩位小數(shù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • C語言中的strlen()和sizeof()對比分析

    C語言中的strlen()和sizeof()對比分析

    這篇文章主要介紹了C語言中的strlen()和sizeof()區(qū)別對比,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-03-03
  • typedef_struct與struct之間的區(qū)別

    typedef_struct與struct之間的區(qū)別

    本篇文章主要是對typedef struct與struct之間的區(qū)別進(jìn)行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2013-12-12
  • Opencv2.4.9函數(shù)HoughLinesP分析

    Opencv2.4.9函數(shù)HoughLinesP分析

    這篇文章主要為大家詳細(xì)介紹了Opencv2.4.9函數(shù)HoughLinesP,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-01-01

最新評論

吴江市| 凌源市| 碌曲县| 宝兴县| 财经| 南安市| 大丰市| 长沙县| 泰安市| 剑阁县| 怀集县| 仙桃市| 周口市| 深圳市| 梨树县| 富阳市| 嵊州市| 南投市| 乐都县| 广汉市| 临安市| 乌恰县| 嘉峪关市| 吉木乃县| 津市市| 左权县| 龙南县| 晋江市| 泾阳县| 揭东县| 宜君县| 罗田县| 甘谷县| 遵义县| 基隆市| 图们市| 略阳县| 大石桥市| 富源县| 肇东市| 揭阳市|