当前位置: C语言 -- 基础 -- 声明

声明(十一)

3、restrict类型限定符

与其它类型限定符一样,restrict类型限定符仅对左值表达式有意义。除指向对象类型的指针类型和元素类型是指向对象类型的指针类型的数组类型外,其它类型不能使用restrict类型限定符限定。

int * restrict ptr;	//合法
int * restrict arr[3];	//合法

使用restrict类型限定符可以声明受限指针(restricted pointer),受限指针与其指向对象之间存在一种特殊关系:在受限指针的生命周期内,如果受限指针指向对象发生改变,所有对该对象的访问(包括读操作和写操作)都必须直接或者间接地通过受限指针完成;否则其行为是未定义的。

int * restrict p1;
int * restrict p2;

假设指针p1p2具有文件作用域,指针p1指向某对象,并且该对象在程序中被修改,那么该对象就不能使用指针p2访问。


如果指向对象的值未发生改变,两个受限指针可以指向同一对象。

void f(int N, int * restrict p1, const int * restrict p2, const int * restrict p3)
{
    for(int i=0; i<N; i++)
        p1[i] = p2[i] + p3[i];
}
...
int a1[5];
const int a2[] = {0, 2, 4, 6, 8};

f(5, a1, a2, a2);   //合法

受限指针与指向对象间的特殊关系并不是因为使用restrict类型限定符确定的,而是通过编程人员编写代码确定的;使用restrict类型限定符的目的是告诉编译器这种关系,编译器可以在此基础上进行优化。具体实现中,会不会根据restrict类型限定符进行优化,以及如何优化将由实现决定。删除restrict类型限定符不会改变程序行为,除非使用_Generic区分一个类型是否使用restrict类型限定符限定。

int arr[10] = {0};
int *p1 = arr;
int * restrict p2;

p2 = calloc(10, sizeof(int));

for(int i=0; i<10; i++)
{
    p1[i] -= 1;
    arr[i] += 2;
    p1[i] += 1;

    p2[i] -= 1;
    p2[i] += 2;
    p2[i] += 1;
}

受限指针p2是访问动态内存的唯一方式,使用restrict类型限定符告诉编译器可以执行某些优化,例如:将p2[i] -= 1;语句、p2[i] += 2;语句以及p2[i] += 1;语句优化为一个语句p2[i] += 2;。指针p1不能执行类似优化,因为指针p1可能不是访问指向对象的唯一方式。


受限指针可以给非受限指针赋值;但受限指针间的赋值却是未定义行为,受限指针间的赋值存在一种例外情况:在嵌套块中,如果受限指针是由外向内赋值,其行为是允许的。

{
    int * restrict outer1;
    int * restrict outer2;
    int *ptr;
    
    ptr = outer1;	//合法。
    outer2 = outer1;	//未定义行为。
    
    {
        int * restrict inner = outer1;    //合法。
    }
}

函数声明或者函数定义中,如果两个形式参数使用restrict类型限定符限定,则表示函数不会使用这两个参数来获取相同的对象,即这两个参数指向的对象在内存中没有重叠。这种情况下编译器可以仅根据形参声明,而不需要分析函数体作出优化判断。

#define LENGTH 10

void func(int number, int * restrict p1, int * restrict p2)
{
    while(number-- > 0)
        *p1++ = *p2++;
}
...
int arr[LENGTH] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
func(LENGTH/2, arr+LENGTH/2, arr);  //合法。
func(LENGTH/2, arr+1, arr);         //未定义行为。

func(LENGTH/2, arr+LENGTH/2, arr)函数调用中,通过第一个指针可以访问数组的后5个元素,通过第二个指针可以访问数组的前5个元素,两者之间没有重叠,所以是合法的。

func(LENGTH/2, arr+1, arr)函数调用中,通过第一个指针可以访问数组第2至第6个元素,通过第二个指针可以访问数组第1至第5个元素,两者之间有多个元素是相同的,所以其行为是未定义的。


受限指针声明所在的块结束执行时,受限指针的生命周期将结束。如果要将受限指针的值传出其声明所在块,一种解决方法是将受限指针声明为结构成员。

typedef struct {
    int number;
    double * restrict ptr;
} S;

S func(int number)
{
    S data;
    
    data.number = number;
    data.ptr = calloc(number, sizeof(double));
    
    return data;
}