1. 程式人生 > >C語言:幾個不常用(或者說可能出錯的如const)的type qualifier

C語言:幾個不常用(或者說可能出錯的如const)的type qualifier

幾個不常用(除const和volatile外)的type qualifiers,const和volatile應該算是最熟悉的了,但是其他的幾個就不大容易在程式碼中發現,寫下來順便學習下
描述:這幾個type qualifiers的使用範圍是有限制的,const和volatile可以使用在任何資料型別上,而restrict只能使用在指標上(不過也看見過直接使用在變數上的),當將一個qualifier用在一個數組上的時候,這個qualifier修飾的是該陣列中的每個元素,而不是這個陣列本身!。
const
用const來修飾一個變量表示該變數的內容不可更改,用來保護變數。const的修飾作用物件的解釋和例子:
const int x = 4 ;       /*x這個int型別的變數的值不可更改*/
const int *z;    /*z是一個指向const int型別的指標,所以z所指的物件的值不可更改,但是z這個指標本身可更改*/
int * const ptr;   /*一個const型別的指標ptr,它指向一個int型別的資料,這個指標ptr本身不能更改(不能一會兒指向B一會兒指向C,它只能指向它的初始化值所指向的地方),但是ptr所指向的記憶體區內的內容可以被更改*/
const int * const p;  /*指標p本身不能被更改,同時它所指向的物件也不能被更改*/

const 和volatile聯合在一起使用(看起來是不是有點怪怪的??)。設想一種情況,程序間使用共享記憶體的方式通訊,例如在這個共享記憶體區中存放一個int資料,在其中一個程序(process 1)中將這個int資料用const修飾(該程序只讀這個int資料)而在另一個程序(process 2)中不加任何修飾(該程序讀寫該int資料),在這中情行下,process 1雖然宣告這個int為const,但是也不能保證它的值不變(它只能保證它自己不去修改它),這時就可以將其在process 1中宣告為volatile const int型別。

一個const指標可以被初始化指向一個非const資料,但是不能用這個指標來修改這個資料的值。

如果將一個const資料的地址賦值給一個非const的指標,然後再通過這個指標來修改該const資料的值將發生不可預知的事情。(這一點沒有確認過,因為曾經看見過一個例子講如何修改一個const資料的值,它使用的就是這個辦法。)
volatile

volatile用來禁止編譯器對變數引用的優化。該qualifier強制編譯器為其所修飾的物件分配記憶體空間,並且每次訪問該物件都從記憶體中讀取。
volatile物件的地址可以被賦值給一個非volatile的指標,同樣一個非volatile物件的地址也能夠被賦值給一個volatile的指標。

結構體(struct)、共用體(union)和列舉型別(enumeration)的tag不包含const或者volatile修飾符,如:
volatile struct whale {
int weight;
char name[8];
} beluga;
struct whale blue;
在這幾句程式碼中,beluga為volatile但是blue不是。

__unaligned

Use this data-type qualifier in pointer definitions to indicate to the compiler that the data pointed to is not
properly aligned on a correct address. (To be properly aligned, the address of an object must be a multiple of
the size of the type. For example, two-byte objects must be aligned on even addresses.)

When data is accessed through a pointer declared __unaligned , the compiler generates the additional
code necessary to copy or store the data without causing alignment errors. It is best to avoid use of
misaligned data altogether, but in some cases the usage may be justified by the need to access packed
structures, or by other considerations.

Here is an example of a typical use of __unaligned :
typedef enum {int_kind, float_kind, double_kind} kind;
void foo(void *ptr, kind k) {
switch (k) {
case int_kind:
    printf("%d", *(__unaligned int *)ptr);
    break;
case float_kind:
printf("%f", *(__unaligned float *)ptr);
break;
case double_kind:
printf("%f", *(__unaligned double *)ptr);
break;
}
}

__restrict / restrict
這是從IBM的一個網站上看來的:
restrict只可以用在指標身上。如果一個指標被restrict修飾,那麼就在它(這個指標)和它所指向的物件之間建立了一種特殊的聯絡──只能用這個指標或者這個指標的表示式來訪問這個物件的值。
一個指標指向一個記憶體地址。同一塊記憶體可以由多個指標來訪問並在程式執行時修改它(這塊記憶體)。restrict告訴編譯器,如果一塊由一個被restrict修飾的指標所指向的記憶體被修改了,那麼沒有其它的指標可以來訪問這塊記憶體。編譯器可能會選擇一種方式來優化程式碼中呼叫被restrict修飾的指標的部分,這可能導致錯誤發生。程式設計師有責任來確保正確地按照他們所設想的來使用被restrict修飾的指標,否則的話,可能會發生意想不到的結果。
如果一塊特定的記憶體區沒有被修改,那麼它可以被多個restrict指標(被restrict修飾的指標)所指代(或者叫做引用或訪問)。
另外,restrict指標的賦值是有限制的,這一點在函式呼叫和巢狀塊(
nested block之間是沒有區別的。在包含restrict指標的塊中,只能將外層的restrict指標的值賦給內層的restrict指標,在同層內不可以相互賦值,當然在外層不可能賦以內層的值。一個例外是,當一個宣告restrict指標的程式碼快執行完後,這個restrict指標所指的記憶體就可以被其它的指標訪問了(因為那個restrict是那個程式碼塊的區域性變數,當執行完那個程式碼塊後,這個指標也就不復存在了)。


原文:
The restrict type qualifier may only be applied to a pointer. A pointer declaration that uses this type qualifier
 establishes a special association between the pointer and the object it accesses, making that pointer and
expressions based on that pointer, the only ways to directly or indirectly access the value of that object.

A pointer is the address of a location in memory. More than one pointer can access the same chunk of
memory and modify it during the course of a program. The restrict type qualifier is an indication to the
compiler that, if the memory addressed by the restrict-qualified pointer is modified, no other pointer will
access that same memory. The compiler may choose to optimize code involving restrict-qualified pointers in a
way that might otherwise result in incorrect behavior. It is the responsibility of the programmer to ensure that
restrict
-qualified pointers are used as they were intended to be used. Otherwise, undefined behavior may
result.

If a particular chunk of memory is not modified, it can be aliased through more than one restricted pointer.


The following example shows restricted pointers as parameters of foo(), and how an unmodified object can
be aliased through two restricted pointers.
void foo(int n, int * restrict a, int * restrict b, int * restrict c)
{
int i;
for (i = 0; i < n; i++)
a[i] = b[i] + c[i];
}

Assignments between restricted pointers are limited, and no distinction is made between a function call and
an equivalent nested block.
{
int * restrict x;
int * restrict y;
x = y; // undefined
{
int * restrict x1 = x; // okay
         int * restrict y1 = y; // okay
x = y1; // undefined
}
}

In nested blocks containing restricted pointers, only assignments of restricted pointers from outer to inner
blocks are allowed. The exception is when the block in which the restricted pointer is declared finishes
execution. At that point in the program, the value of the restricted pointer can be carried out of the block in
which it was declared.

還有幾個如auto、register、static和extern等type qualifiers就太常見了,沒有必要記錄下來,應該非常熟悉才對。