restrict / __restrict / __restrict__ 关键字

最近在RHEL7上使用字符串拷贝函数wcscpy(),发现出来的结果不对,好像是dest的缓冲区被破坏了:

int main()

{
        wchar_tbuf[256] = L"\n \n Total memory used\n Total buffer size\n \n Totalphysical memory used\n Total memory actually used.\n";

        wchar_tnlStr[5] = L"\n ";

        wchar_t *tmp =buf;

        int nlStrLen =wcslen(nlStr);

 

        while (tmp =wcsstr(tmp, nlStr))

        {  

                wcscpy(tmp + nlStrLen -1, tmp + nlStrLen);

                 tmp ++;

        }  

       printf("%ls",buf);

       return 0;

}


这段代码的作用是将buf中的"\n"后的空格去掉,关键是对wcscpy()的调用。wcscpy()将src字串拷贝到dest。而本例中,src与dest出现了内存上的交叠。

一般来讲,src与dest的交叠方式分两种 (如下图):

1. dest的头与src的尾相交叠。

2. src的头与dest的尾相交叠,本例尾这种情况。

 

虽然manpage对wcscpy()的说明中强调了,src与dest不应该交叠,但分析wcscpy()的实现逻辑,发现对于交叠情况2,也应该正确工作,因为wcscpy()总是从src的头开始向dest的头拷贝数据,所以当拷贝指针移动到src的尾部并拷贝数据写入dest的尾部,也即src的头部时,并不会损害dest。

但是现在问题来了,最终输出的结果显示buf仍然被损坏了:

Toaal memory used

Total buffer siz

 

 

Ttaal physiclmmeemory used

Toaal memory actually used.

这其实问题是一个很出名的问题:即*cpy系列的函数不能正确处理src与dest内存交叠的情况。附一篇大牛们为该问题吵架的连接:

https://sourceware.org/bugzilla/show_bug.cgi?id=12518


回答也非常简单:

当src与dest内存有交叠时,改用*move系列函数: memmove / wmemmove。


只是,我感兴趣为什么这个问题会出现?

如果把wcscpy()的实现拷贝来放到本地,名字改成wcscpy_local 并调用之,发现工作正常了,附上wcscpy_local()的实现:

char_t* wcscpy_local (wchar_t * dest, const wchar_t * src)

{
    wint_t c;

    wchar_t*wcp;

    if(__alignof__ (wchar_t) >= sizeof (wchar_t))
    {
        constptrdiff_t off = dest - src - 1;
        wcp = (wchar_t *) src;

        do
        {
            c =*wcp++;
            wcp[off] = c;
        }
        while(c != L'\0');
    }
    else
    {
        wcp =dest;
        do
        {
            c = *src++;
            *wcp++ = c;
        }
        while (c != L'\0');
    }
    return dest;

}

那么,为什么调用系统提供的wcscpy就会出问题呢?


我查到的理解是:系统定义的wcscpy原型中,参数多了一个限制符“__restrict”:

File: /usr/include/wchar.h

extern wchar_t *wcscpy (wchar_t *__restrict __dest, constwchar_t *__restrict __src) __THROW;

 

关于restrict

在C99中,restrict是一个关键字,而C++中却没有该关键字,但当代编译器也在C++实现了差不多的功能,用的关键字是__restrict,或__restrict__。(为了简便,以下只称restrict)

restrict 用来定义指针变量,表明该变量没有别名,意思就是:除了该变量以外,没有别的方法可以访问其指向的地址空间。

举个有别名的例子:

int *pA, *pB;

pB = pA;  //那么pB就是pA的别名,通过pB照样可以访问pA指向的内容


编译器如果知道一个变量没有别名,那么,它就会放心大胆的作优化,比如这段代码:

void update(int *ptrA, int * ptrB, int * val)
{
          *ptrA +=*val;
          *ptrB +=*val;
}

编译出来的ASM类似于:

mov eax val;
add ptrA eax;
mov eax val;
add ptrB eax;

其中,变量val加载了两次。因为它不知道ptrA,ptrB是否与val有内存上的交叠,所以为了保险起见,只能每次用到val时都从内存里读。

如果加上__restrict:

void update_restrict(int * __restrict ptrA, int * __restrictptrB, int * __restrict val);

则汇编变成(需要打开编译优化选项,底下有讲到):

mov eax val;
add ptrA eax;
add ptrB eax;

可看到,val只加载了一次,因为编译器知道val没有别名,不会被别人改变,所以可以重复使用寄存器里的值。

 

猜想wcscpy的错误原因

那么,我就在想,因为wcscpy的参数加了__restrict,这就要求src与dest不能出现内存交叠,而事实上,我们传进的参数没有保证这点,导致了优化后的结果不正确。

不过我并没能重现个问题,即便对自己版本的wcscpy_local()参数加上__restrict,并打开编译优化选项,仍然没有重现问题。

抛砖引玉,希望有人解开我的疑惑~

 

restrict/__restrict/__restrict__的编译选项:

为了观察__restrict的汇编结果,需要打开优化选项,以下是GCC / G++的选项:

对C:

-O3 -std=c99

对C++,-std=c99已经默认开启,因此只用:

-O3

 

参考:

(1) WiKi: http://en.wikipedia.org/wiki/Restrict

(2) Demystifying The Restrict Keyword:

http://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值