最近在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 usedTotal buffer sizTtaal physiclmmeemory usedToaal 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

restrict / __restrict / __restrict__ 关键字相关推荐

  1. Understanding C/C++ Strict Aliasing

    Understanding C/C++ Strict Aliasing 深入理解C/C++中的`Strict Aliasin`规则 or - Why won't the #$@##@^% compil ...

  2. linux vim配置c,Linux入门学习教程:GNU C及将Vim打造成C/C++的半自动化IDE

    C语言在Linux系统中的重要性自然是无与伦比.不可替代,所以我写Linux江湖系列不可能不提C语言.C语言是我的启蒙语言,感谢C语言带领我进入了程序世界.虽然现在不靠它吃饭,但是仍免不了经常和它打交 ...

  3. linux编译c 自动化,Linux江湖06:感悟GNU C以及将Vim打造成C/C++的半自动化IDE

    C语言在Linux系统中的重要性自然是无与伦比.不可替代,所以我写Linux江湖系列不可能不提C语言.C语言是我的启蒙语言,感谢C语言带领我进入了程序世界.虽然现在不靠它吃饭,但是仍免不了经常和它打交 ...

  4. NVIDIA GPU SM和CUDA编程理解

    SM硬件架构基础 不同架构的变化可以参考: ​​​​​​从AI系统角度回顾GPU架构变迁--从Fermi到Ampere(V1.2) - 知乎 英伟达GPU架构演进近十年,从费米到安培 - 知乎 Vol ...

  5. 6.CUDA编程手册中文版---附录AB

    附录A 支持GPU设备列表 更多精彩内容,请扫描下方二维码或者访问https://developer.nvidia.com/zh-cn/developer-program 来加入NVIDIA开发者计划 ...

  6. linux模块移植到freertos,opus移植到freertos系统

    硬件平台:cortex-M4F 200MHZ平台(RTL8721DM) 软件系统:FREERTOS 编译器: Using built-in specs. COLLECT_GCC=/home/kuili ...

  7. linux标准c和c编译器6,Linux折腾记(六):感悟GNU C及把Vim打造成C/C++的半自动化IDE...

    C语言在Linux系统中的重要性自然是无与伦比.不可替代,所以我写Linux江湖系列不可能不提C语言.C语言是我的启蒙语言,感谢C语言带领我进入了程序世界.虽然现在不靠它吃饭,但是仍免不了经常和它打交 ...

  8. linux 中输入一个c程序,从c源程序到Linux可执行代码的过程

    你写了一个C程序,然后用gcc编译之后得到一个可执行程序.看起来相当简单,是吗? 你有没有想过编译的过程中发生了什么,C程序怎么转变成二进制程序的呢? 其实,源程序最终成为可执行程序经历了如下4个阶段 ...

  9. 《编写高质量代码:改善c程序代码的125个建议》——第1章 数据,程序设计之根本建议1:认识ANSI C...

    本节书摘来自华章计算机<编写高质量代码:改善c程序代码的125个建议>一书中的第1章,建议1,作者:马 伟 更多章节内容可以访问云栖社区"华章计算机"公众号查看. 第1 ...

最新文章

  1. 架构选型必读:集中式与分布式全方位优劣对比
  2. 15.4. syslog, klogctl - read and/or clear kernel message ring buffer; set console_loglevel
  3. 终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的
  4. [MSSQL]也说SQL中显示星期几函数
  5. GitHub使用入门讲解--官方文档翻译让你最真实了解
  6. Python之split()函数
  7. oracle用户密码规则,使用Oracle自带profile以及函数简单设定Oracle用户名密码规则...
  8. java后端简历项目经历_java程序员简历项目经验怎么写
  9. Java三大体系JavaSE、JavaEE、JavaME的区别
  10. 核磁共振成像基本原理——杨正汉(1)
  11. 推荐一个文字生成图片的网站
  12. System Repair Engineer (SREng) 2.5 常用操作
  13. PostgreSQL12中文手册
  14. mybatis plus(包米豆)json存储Mysql数据库
  15. C# 批量图片打包下载
  16. 快手科技2020年总收入人民币588亿元,同比增长50.2%
  17. mysql数据库行列矩阵调换位置(行与列调换)
  18. 请教dalao,为什么运行时二三步会合并到一起?
  19. 在 TensorFlow 上使用 LSTM 进行情感分析
  20. 前端面试题《CSS》

热门文章

  1. ZeroBrane Studio远程调试Cocos2d-x的Lua脚本
  2. Linux红旗语言,如何改变红旗linux控制面板语言
  3. Java开发者环境搭建
  4. MAE TransMix
  5. arduino 与java通信_在Java应用程序与Arduino Uno之间建立串口连接
  6. 求n的阶乘及1~n的阶乘之和
  7. python实现zigzag_Zigzag Iterator的Pythonic方式?
  8. python生成链接二维码
  9. CentOS7-samba文件共享服务
  10. 图文解读Fcoin发布的FT公链