C語言類型限定符(type specifier)(二)——restrict和_Atomic詳細教程

前言:C語言中的類型限定符一共有四個,const,volatile,restrict,_Atomic,前面的一片文章詳細介紹了volatile的作用以及使用方法,本文爲系列文章第二篇,介紹接下來的兩個類型限定符,restrict,和 _Atomic,前一篇文章請參考:

C語言類型限定符(type specifier)(一)——volatile詳細教程

一、restrict類型限定符

(1)restrict的限制作用——是給人看的

restrict的意思就是限定,限制,那到底是限定什麼?限制什麼呢?實際上,這個關鍵字只能夠用於指針,當然這個指針可以是聲明的變量,也可以是函數的參數,作用就是表明這個指針是初始化數據塊以及訪問這個數據塊唯一的方式,而不通過其他變量或者是指針改變,這不就是一種限制 一種限定嘛

到底怎麼用呢?看一個簡單的例子:

int a;
int * restrict p = &a;
*p = 200;  //通過指針改變它的值
a = 100;   //通過它本身改變它的值    
printf("a is %d.\n", a); //輸出的結果是100

這裏一定會疑惑,不是說被restrict修飾指針纔是初始化以及改變它所指向的變量值的唯一方式麼,這裏爲什麼會通過a自己去改變還行,居然沒有報錯,實際上restrict只是一個約定,而不是強制約束,即告訴我們編寫代碼的人,這個指針是一個被restrict修飾的指針,那麼如果想要初始化或者是修改它所指向變量的值,最好就通過這個rerestrict修飾的指針來完成,而不要通過其他的方式,當然是用其他的方式也是可以的,只是不建議這麼做。

爲什麼要有一種這樣的約定呢?是爲了讓編譯器能夠更好的優化一部分代碼,具體怎麼優化呢?參見下面的代碼:

(2)restrict的優化作用——給編譯器看的

舉個簡單的例子

int foo (int* x, int* y) {
    *x = 0;
    *y = 1;
    return *x;
}

很顯然函數foo()的返回值是0,不管給x,y傳遞進去什麼參數,返回的都是0。雖然我們人的的確確知道,這個函數的返回值永遠是0,但是編譯器不知道啊,編譯器只知道它總是返回x,所以編譯沒辦法優化它,編譯器不能將原有代碼替換成下面的更優版本

int f (int* x, int* y) {
    *x = 0;
    *y = 1;
    return 0;  //注意:這裏是不能替換哦!!!
}

但是,我們現在使用restrict來試一下,看看,現在我們有了restrict這個關鍵字,就可以利用它來幫助編譯器安全的進行代碼優化了,到底怎麼優化呢?看下面的例子,將參數用restrict進行約束

int f (int *restrict x, int *restrict y) {
    *x = 0;
    *y = 1;
    return *x;
}

現在不僅我們人知道,要改變x,y這兩個指針所指向的值,通過x,y是唯一的方式,不僅如此現在編譯器也知道了這件事,它知道沒有其他的方式來改變x,y所指向的值,而在代碼中,改變x所指向的值的語句只有一句,那就是

*x = 0

此時,由於指針 x 是修改 *x的唯一途徑,編譯起可以確認 “*y=1; ”這行代碼不會修改 *x的內容,也沒有其他代碼修改x的值,因此可以安全的優化爲

int f (int *restrict x, int *restrict y) {
    *x = 0;
    *y = 1;
    return 0;  //這個地方是可以優化的
}

這就是restrict爲什麼告訴編譯器來優化自己的原因了,當然了是不是說這個restrict一定是一種強制執行的約束呢?當然不是了,

看下面的代碼

#include <stdio.h>
#include <stdlib.h>

int f (int *restrict x, int *restrict y) 
{
    *x = 0;
    int *px = x;
    *px=100;     //通過其他方式改變了x的值,也是正確的
    *y = 10000;  //
    return *x;   //所以返回的值100
}

int main(int argc, char **argv)
{
   
    int xx=10;
    int yy=20;
    int *x=&xx;
    int *y=&yy;
    int result=f(x,y);
    
	printf("result is %d.\n", result);

    getchar();
	return 0;
}

我們發現,我們依然是可以通過其他的方式來改變restrict所指向變量的值,所以這種約束並不是強制執行的,只不過這個時候如果編譯器再進行優化,將返回值優化成結果 0 ,就會出錯了,因爲已經有別的指針 px ,改變了x的值了

 下面總結關於restrict類型限定符的一些說明

(1)restrict是C99的內容,C++中不支持該類型約束關鍵字,另外MSVC中也是不支持這個關鍵字的,他屬於標準C的內容,經試驗,MingW中GCC支持restrict;

(2)restrict更多的是給人看的,告訴人不要通過其他方式改變這個指針所指向的變量,但是這種約束並不是強制約束哦;

(3)restrict只能用於修飾指針,包括指針變量和指針形參;

(4)個人覺得restrict在性能上的優化微乎其微,沒什麼太大作用,可能這也是它不被廣泛支持的原因之一吧!!

二、_Atomic類型限定符——C11,在stdatomic.h頭文件

這個關鍵字比較簡單,

併發程序的設計往往是通過多個線程來實現的,多線程最大的問題在於資源的競爭,即多個線程同時競爭使用某一個資源,這可能導致一些致命的問題,一種解決方式是通過添加“鎖”的方式,保證及時的加鎖以及鎖的釋放,保證在某一個時刻始終只有一個線程在使用該資源,CII中引入了一個新的類型修飾符,也可以達到同樣的效果。

你如下面的操作

int age;
age = 25;

這只是普通的變量定義與複製操作,如果有多個線程同時操作age,則會造成資源的競爭,怎麼辦呢?C11中可以這樣做

_Atomic int age; //通過_Atomic聲明這個變量是一個“原子類型”

atomic_store(&age, 25); //通過這個宏函數來給原子變量賦值

這樣保證了當一個線程在對一個原子類型的對象進行操作時,其他線程是不能夠訪問該原子對象的,防止了資源競爭

總結:

(1)C11中引入了原子類型修飾符_Atomic,在 stdatomic.h 頭文件中,該頭文件中定義了很多的宏函數來操作原子類型,當然適用的前提條件是編譯器必須要支持這個C11新特性;

(2)當一個線程在對一個原子類型的對象進行操作時,其他線程是不能夠訪問該原子對象的,防止了資源競爭

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章