文章目錄
- 8.11 指針類型轉換(Pointer type conversions)
- Rule 11.1 不能在函數指針和任何其他類型指針之間進行轉換
- Rule 11.2 不得在指向不完整類型的指針和其他任何類型間進行轉換
- Rule 11.3 不得在指向不同對象類型的指針之間執行強制轉換
- Rule 11.4 不得在指向對象的指針和整數類型之間進行轉換
- Rule 11.5 不得將指向 void 的指針轉換為指向對象的指針
- Rule 11.6 不得在指向 void 的指針和算術類型之間執行強制轉換
- Rule 11.7 不得在指向對象的指針和非整數算術類型之間執行強制轉換
- Rule 11.8 強制轉換不得從指針指向的類型中刪除任何 const 或 volatile 限定符
- ule 11.9 宏“NULL”是整數型空指針常量的唯一允許形式
8.11 指針類型轉換(Pointer type conversions)
指針類型可分為以下幾種:
?指向對象的指針;
?指向函數的指針;
?指向不完整的指針;
?指向void 的指針;
?空指針常量,即值0,可選擇強制轉換為void *
標準標準允許的涉及指針的轉換只有以下幾種:
?指針類型到void類型的轉換;
?指針類型到算術類型的轉換;
?從算術類型到指針類型的轉換;
?從一種指針類型轉換為另一種指針類型
盡管語言約束允許,指針和除整數類型以外的任何算術類型之間的轉換是不需要定義的。
以下允許的指針轉換不需要顯式強制轉換:
?指針類型到_Bool類型的轉換(僅C99);
?從空指針常量到指針類型的轉換;
?從指針類型轉換為兼容的指針類型,前提是目標類型具有源類型的所有類型限定符;
?指向對象或不完整類型的指針與void *或其限定版本的指針之間的轉換,前提是目標類型具有源類型的所有類型限定符。
在C99中,任何不屬于指針轉換子集的隱式轉換都違反了約束(C99第6.5.4節和6.5.16.1節)。
在C90中,任何不屬于指針轉換子集的隱式轉換都會導致未定義的行為(C90第6.3.4節和6.3.16.1節)。
指針類型和整數類型之間的轉換需要實現定義
Rule 11.1 不能在函數指針和任何其他類型指針之間進行轉換
等級:必要
分析:可判定,單一編譯單元
適用:C90,C99
展開:指向函數的指針只能轉換為或從指向具有兼容類型的函數的指針轉換而來
原理: 指向函數的指針與以下任何一種的轉換,都將導致不確定的行為:
?指向對象的指針;
?指向不完整的指針;
?void *
如果通過類型與被調用函數不兼容的指針調用函數,則該行為是未定義的。標準允許將指向函數的指針轉換為指向不同類型函數的指針。標準也允許將整數轉換為指向函數的指針。但是,為了避免由于使用不兼容的指針類型調用函數而導致的未定義行為,這兩種方法都被該規則禁止。
例外:
- 空指針常量可以轉換為指向函數的指針;
- 指向函數的指針可以轉換為void;
- 函數類型可以隱式地轉換為指向該函數類型的指針
注意:例外3涵蓋了C90第6.2.2.1節和C99節中描述的隱式轉換
6.3.2.1部分。這些轉換通常發生在以下情況:
?直接調用函數,即使用函數標識符來表示要調用的函數;
?將函數賦值給函數指針
示例:
typedef void (*fp16) (int16_t n);
typedef void (*fp32) (int32_t n);#include <stdlib.h> /* 獲取宏 NULL */
fp16 fp1 = NULL; /* 合規 - 例外 1 */
fp32 fp2 = (fp32)fp1;/* 違規 - 前后函數指針指向類型不同 */if (fp2 != NULL) /* 合規 - 例外 1 */
{
}fp16 fp3 = (fp16)0x8000; /* 違規 - 整型數轉換為函數指針 */
fp16 fp4 = (fp16)1.0e6F; /* 違規 - 浮點數轉換為函數指針 */
在以下示例中,函數調用返回指向函數類型的指針。 將返回值強制轉換為void 符合此規則。
typedef fp16 (*pfp16)(void);
pfp16 pfp1;
(void)(*pfp1()); /* 合規 - 例外 2 - 函數指針換行為void */
以下示例顯示了從函數類型(標識符)到指向該函數類型的指針的合規的隱式轉換
extern void f(int16_t n);
f(1); /* 合規 - 例外 3, 函數f向函數指針的隱式轉換 */
fp16 fp5 = f; /* 合規 - 例外 3 */
解讀:函數指針不能與其他指針進行轉換,目前函數指針使用的比較少,需要進行檢查。
Rule 11.2 不得在指向不完整類型的指針和其他任何類型間進行轉換
等級:必要
分析:可判定,單一編譯單元
適用:C90,C99
展開:指向不完整類型的指針不得轉換為其他類型。
不得將其他類型轉換為指向不完整類型的指針。
此規則不適用于指向 void 的指針,盡管指向 void 的指針也是指向不完整類型的指針,它們將被 Rule 11.5 涵蓋。
原理: 普通指針與指向不完整類型間的轉換可能會導致指針未正確對齊,從而導致行為不確定。
將不完整類型的指針轉與浮點型互相轉換,也會導致未定義的行為。
指向不完整類型的指針有時用于隱藏對象的表示。將指向不完整類型的指針轉換為指向對象的指針將破壞這種封裝。
例外
- 空指針常量(NULL)可以轉換為指向不完整類型的指針。
- 指向不完整類型的指針可能會轉換為 void。
示例
struct s;
struct t;
struct s *sp; /* 不完整類型1 */
struct t *tp; /* 不完整類型2 */int16_t *ip;
#include <stdlib.h> /* 獲取宏 NULL */
ip = (int16_t *)sp; /* 違規, 不完整類型轉為int16_t * */
sp = (struct s *)1234; /* 違規 常數轉為不完整類型 */
tp = (struct t *)sp; /* 違規 - 不完整類型間轉換 */
sp = NULL; /* 合規 - 例外 1 */
/* */ struct s *f(void);
(void)f(); /* 合規 - 例外 2 */
解讀:結構體類型的指針與其他類型的指針不能強制轉化,但結構體類型的指針可以賦值NULL,也可以轉為void型指針
Rule 11.3 不得在指向不同對象類型的指針之間執行強制轉換
等級:必要
分析:可判定,單一編譯單元
適用:C90,C99
展開:此規則適用于指針所指向的非限定類型。
原理: 指向不同對象的指針間的轉換可能會導致指針未正確對齊,從而導致未定義的行為。
即使已知轉換會產生正確對齊的指針,使用該指針訪問對象的行為也可能是未定義的。例如,如果將類型為 int 的對象作為 short 進行訪問,則即使 int 和 short 具有相同的表示和對齊要求,其行為也是未定義的。
例外:
將指向對象類型的指針轉換為指向對象類型為 char,signed char 或 unsigned char 之一的指針是被允許的。C 標準確保了可以使用指向這些類型的指針來訪問對象的各個字節。
示例:
uint8_t *p1;
uint32_t *p2;
/* 違規,轉為4字節對齊 */
p2 = ( uint32_t * ) p1;
extern uint32_t read_value ( void );
extern void print ( uint32_t n );
void f ( void )
{uint32_t u = read_value ( );uint16_t *hi_p = ( uint16_t * ) &u; /* 違規 - 即使可能正確對齊了 */*hi_p = 0; /* 嘗試在大端模式的機器上清除高16位 */print ( u ); /* 上一行代碼可能不被執行 */
}
下面的示例是兼容的,因為該規則適用于未限定的指針類型。它不會阻止將類型限定符添加到對象類型中
const short *p;
const volatile short *q; q = (const volatile short *)p; /* 合規,都是short型指針 */
以下示例是不合規的,因為不限定的指針類型是不同的,即“指向const限定int的指針”和“指向int的指針”
int * const * pcpi;
const int * const * pcpci; pcpci = (const int * const *)pcpi;
解讀:實際有可能會出現轉換的情況,這個規則可以加上,使用時需要注意,可以進行評審來消除風險。實際使用時,轉換可以提高訪問的效率,例如,指針取值時定義的時uint8 *,但在某時刻使用時,是可以按16bit訪問的,那么轉為uint16 *會更加方便一些。
Rule 11.4 不得在指向對象的指針和整數類型之間進行轉換
等級:建議
分析:可判定,單一編譯單元
適用:C90,C99
展開:指針不應轉換為整型數。
整型數也不應轉換為指針。
原理: 將整數轉換為指向對象的指針可能會導致指針未正確對齊,從而導致未定義的行為。
將對象的指針轉換為整數可能會產生無法以所選整數類型表示的值,從而導致未定義的行為。
注意:在<stdint.h>中聲明的C99類型intptr_t和uintptr_t分別是能夠表示指針值的有符號和無符號整數類型。盡管如此,該規則不允許在指向對象的指針和這些類型之間進行轉換,因為它們的使用并不能避免與指針未對齊相關的未定義行為。
指針和整數類型之間的強制轉換應盡可能避免,但在尋址內存映射寄存器或其他特定硬件特性時可能需要。如果在整數和指針之間進行強制轉換,應注意確保所產生的指針不會引起規則Rule 11.3中討論的未定義行為。
例外:具有整數類型的空指針常量(NULL)可以轉換為指向對象的指針。
示例
uint8_t *PORTA = ( uint8_t * ) 0x0002; /* 違規,整數轉指針*/
uint16_t *p;
int32_t addr = ( in t32_t ) &p; /* 違規,指針轉整數 */
uint8_t *q = ( uint8_t * ) addr; /* 違規,整數轉指針 */
bool_t b = ( bool_t ) p; /* 違規,指針轉整數 */
enum etag { A, B } e = ( enum etag ) p; /* 違規,指針轉整數 */
解讀:嵌入式中很容易出現整數轉指針的情況,因為要取寄存器中的值。所以該規則可能需要進行裁剪
Rule 11.5 不得將指向 void 的指針轉換為指向對象的指針
等級:建議
分析:可判定,單一編譯單元
適用:C90,C99
原理: 將指向 void 的指針轉換為指向對象的指針可能會導致指針未正確對齊,從而導致未定義的行為。這種轉換應盡可能避免使用,但其也可能有必要使用,例如在使用內存分配功能處理時。如果使用從對象的指針到 void 的指針轉換,則應確保確保生成的任何指針都不會引起規則 11.3 中討論的未定義行為。
例外:類型為指向 void 的指針的空指針常量(NULL)可以轉換為對象的指針。
uint32_t *p32;
void *p;
uint16_t *p16;
p = p32; /* 合規 - 指向uint32_t的指針轉換為指向void的指針 */
p16 = p; /* 違規 */
p = ( void * ) p16; /* 合規 */
p32 = ( uint32_t * ) p; /* 違規 */
解讀:void *不能轉為其他類型指針,NULL除外,主要是防止未對齊導致的異常訪問。
Rule 11.6 不得在指向 void 的指針和算術類型之間執行強制轉換
等級:必要
分析:可判定,單一編譯單元
適用:C90,C99
原理: 將整數轉換為指向void 的指針可能會導致指針未正確對齊,從而導致未定義的行為。
將指向void的指針轉換為整型可能會產生一個不能用所選整型表示的值,從而導致未定義的行為。
任何非整數算術類型和指向void的指針之間的轉換都是未定義的。
例外:可以將值為0的整數常量表達式轉換為指向 void 的指針。(即NULL)
示例:
void * p;
uint32_t u;
/* 違規 - 行為由實現定義 */
p = ( void * ) 0x1234u;
/* 違規 - 未定義行為 */
p = ( void * ) 1024.0f;
/* 違規 - 行為由實現定義 */
u = ( uint32_t ) p;
解讀:這種操作一般很少出現,沒有太大意義。是可以作為檢查項的。
Rule 11.7 不得在指向對象的指針和非整數算術類型之間執行強制轉換
等級:必要
分析:可判定,單一編譯單元
適用:C90,C99
展開:此規則中的“非整數算術類型”表示以下類型之一:
◆ 基本類型是布爾型;
◆ 基本類型是字符型;
◆ 基本類型是枚舉型;
◆ 基本類型是浮點型。
原理: 將基本型為布爾型,字符型或枚舉型的數據轉換為指向對象的指針可能會導致指針未正確對齊,從而導致未定義的行為。
將對象的指針轉換為布爾型,字符型或枚舉型可能會產生無法用所選整數類型表示的值,從而導致未定義的行為。
任何指向對象的指針與浮點型之間的轉換,都將導致未定義的行為。
示例:
int16_t *p;
float32_t f; f = (float32_t)p; /* 違規 */
p = (int16_t *)f; /* 違規 */
解讀:指針關聯地址,而bool,char,enum,float和地址操作無關,這種轉換是異常的操作,是需要進行分析和規避的。
Rule 11.8 強制轉換不得從指針指向的類型中刪除任何 const 或 volatile 限定符
等級:必要
分析:可判定,單一編譯單元
適用:C90,C99
原理: 通過強制轉換來刪除與所指向類型相關的限定條件的任何嘗試都違反了類型限定原則。
注意:這里引用的限定條件與可能應用于指針本身的任何限定條件不同。
如果從所尋址的對象中刪除限定符,可能會出現下述問題:
◆ 刪除 const 限定符可能會繞過對象的只讀屬性并導致其被修改;
◆ 訪問對象時,刪除 const 限定符可能會導致異常。
◆ 刪除 volatile 限定符可能會導致無法訪問已優化的對象。
注意:刪除C99 restrict限制類型限定符是可接受的。
示例
uint16_t x;uint16_t * const cpi = &x; /* 指針常量 */uint16_t * const *pcpi; /* 指向指針常量的指針 */uint16_t * *ppi;const uint16_t *pci; /* 指向常量的指針 */
volatile uint16_t *pvi; /* 指向 volatile 的指針 */uint16_t *pi;
pi = cpi; /* 合規 - 無隱式轉換, 無強制轉換 */
pi = (uint16_t *)pci; /* 違規,const屬性被移除 */
pi = (uint16_t *)pvi; /* 違規,volatile屬性被移除 */
ppi = (uint16_t * *)pcpi; /* 違規,const屬性被移除 */
解讀:指針的const 或 volatile 限定符在轉化時被移除,可能是異常的操作,需要進行檢查。
ule 11.9 宏“NULL”是整數型空指針常量的唯一允許形式
等級:必要
分析:可判定,單一編譯單元
適用:C90,C99
展開: NULL 出現在以下應用場景中時,應將宏 NULL 擴展得出一個值為 0 的整數常量表達式:
◆ 將值賦值給指針;
◆ 作為“==”或“!=”運算符的操作數,其另一個操作數是指針;
◆ 作為“?:”運算符的第二個操作數,其第三個操作數是一個指針;
◆ 作為“?:”運算符的第三個操作數,其第二個操作數是一個指針。
忽略空格和任何括號,任何這樣的整數常量表達式都將代表 NULL 的整個擴展。
注意:允許形式為(void *)0的空指針常量,無論它是否從NULL擴展而來。
原理:使用 NULL 而不是 0 可以清楚地表明要使用空指針常量
示例:在以下示例中,p2 的初始化是合規的,因為整數常量表達式 0 未出現在此規則禁止的場景中。
int32_t *p1 = 0; /* 違規 */
int32_t *p2 = ( void * ) 0; /* Compliant */
在下面的示例中,p2 和(void *)0 之間的比較是合規的,因為整數常量表達式 0 作為強制轉換的操作數出現,而不是在此規則禁止的場景中出現
#define MY_NULL_1 0
#define MY_NULL_2 (void *)0 if (p1 == MY_NULL_1) /* 違規 */
{
}
if (p2 == MY_NULL_2) /* 合規 */
{
}
以下示例合規,因為使用實現提供的宏 NULL 是被允許的,即使該宏擴展為值為 0 的整數常量表達式也是如此。
/* 也可以是 stdio.h, stdlib.h 或其他頭文件 */
#include <stddef.h> extern void f(uint8_t *p); /* 任何NULL的合理定義都是合規的,例如:
* 0
* (void *)0
* (((0)))
* (((1 - 1)))
*/
f(NULL);
解讀:使用空指針時不允許使用0,會產生誤解,應使用NULL,但(void *)0是允許使用的。該規則應該被進行檢查,增強可讀性。