[轉供參考] C11: 致力于更安全编程的新C标准
C11: 致力于更安全编程的新C标准 2014-09-13 16:05:38
本文简要翻译自http://blog. 转载请注明出处.
C11标准化了许多当前主流编译器已经实现了的特性,同时定义了更加适合多线程的内存模型. 换句话说C11是一个更好的C.
C99标准中的问题
C99标准带来了许多新特性,包括:
• 变长数组(Variable length arrays)
• 指定初始化(Designated initializers)
• 泛型数学库
• 新的数据类型: long long, _Complex, _Bool
• restrict指针
• 变量混合声明
• 内联函数
• 单行风格的注释
尽管如此,C99标准依然没能取得巨大成功,即使在今天,兼容C99的实现也是个挑战.
C99标准到底何去何从? 它的一些强制特性被证明在某些平台上很难实现,其它特性也受到了广泛的质疑或被认为是实验性质的以至于某些厂商甚至建议C程序员转向使用C++.
C99不受欢迎, 政治也在其中扮演了角色. 最起码, 在20世纪90年代, C和C++两个委员会之间缺乏合作是个公开的秘密. 好消息是今天这两个委员会之间的合作更好了, C11得以避免C99标准上的设计失误.
新的标准, 新的希望?
C的安全一直是令人关心的问题. 其不安全的特性总是恶意代码的温床, 例如不检查边界的字符串操作函数和不检查参数的文件I/O函数.
C11使用一组更安全的标准函数来解决这些问题, 这组新函数意在替换传统的不安全的函数(尽管后者在C11里依然可用). 此外, C11还包含Unicode支持, 遵循IEC 60559的浮点运算以及IEC 60559的复数运算, 内存对齐工具集, 匿名结构和联合, _Noreturn 函数修饰符, 以及最重要的 – 多线程支持. 没错,我说的就是m-word! (Yes, I said the m-word)
让我们更进一步看看其中一些特性以及其它特性.
多线程
对于普通C程序员来说,C11的最大变化就是对多线程的支持进行了标准化. C当然已经支持多线程且有数十个年头了. 然而, 所有当前流行的C线程库都有很多非标准的扩展,因此变得不可移植.
新的C11头文件<threads.h>声明了创建和管理线程, 信号, 条件变量的函数, 以及_Atomic类型限定符. 另一个新的头文件<stdatomic.h>声明了不可中断对象访问工具. 最后, C11引入一个新的存储类修饰符_Thread_local (等价于C++11 thread_local). 声明为_Thread_local的变量不在多线程之间共享. 确切的说, 每个线程持有变量单独的拷贝.
作为一个内幕, 如果你在正在找一个人为笨拙的关键字_Thread_local负责的话, 就找我吧. 在21世纪早期, 当C++标准委员会致力于多线程支持的时候, 最初的线程本地存储的提议是使用关键字__thread , 由于不能清楚的表达该关键字的意图,因此我认为这既不安全也不易懂 (毕竟, __thread并不创建线程!), 并且还可能碰巧和旧的使用__thread修饰用户声明的标识符的代码冲突. 我建议把__thread改为thread_local并且被接受了. 此后thread_local渗透进了其它编程语言, 包括C11. 礼物和恐吓邮件都是受欢迎的!
C11另一个线程相关的特性是quick_exit()函数, 该函数可以让你在exit()函数不起作用时终止程序, 例如当线程的cooperative cancellation是不可能的时候. quick_exit()函数确保使用at_quick_exit()注册的函数以它们注册顺序的相反顺序被调用. 然后at_quick_exit()调用_Exit(), 同exit()相比该函数不会刷新进程文件描述符.
匿名structs和union
匿名struct或者union既没有标签名也没有类型定义名. 它对嵌套的组合有用, 例如, 结构体的联合成员. 下面的C11代码声明了一个带有匿名union的成员并直接访问了union中的数据成员:
点击(此处)折叠或打开
1. struct T//C++,C11
2. {
3. intm;
4. union//anonymous
5. {
6. char*index;
7. intkey;
8. };
9. };
10. struct T t;
11. t.key=1300;//access the union's member directly
泛型函数
C11依然不支持模板, 但是它支持通过一种基于宏的方法定义“泛型”函数. 新的关键字_Generic用于声明一个泛型表达式, 其可以转换成类型相关的“特化”版本
在下面的例子中, 泛型的立方根计算宏cbrt(X)根据实际参数X的类型判定特化为cbrtl(long double), cbrtf(float) 以及默认的 cbrt(double):
点击(此处)折叠或打开
1. //C11 only
2. #define cbrt(X)_Generic((X),long double:cbrtl,
3. default:cbrt,
4. float:cbrtf)(X)
它是如何工作的呢? 首先参数X 转换为函数参数的特定类型.然后编译器选择匹配的cbrt()变体: 如果X是long double类型为cbrtl(), float类型为cbrtf(), 其它为cbrt().
内存对齐控制
同C++11相似, C11引入了探查以及强制变量和类型的内存对齐工具. _Alignas关键字指明了类型或者对象的对齐请求. alignof 操作符报告它的操作数是否是对齐的. 最后, 函数aligned_alloc()
点击(此处)折叠或打开
1. void*aligned_alloc(size_t algn,size_t size);
分配size字节以algn字节对齐的内存,并返回指向所分配内存的指针.
C11的对齐特性是在新的头文件中<stdalign.h>声明的.
_Noreturn函数修饰符
_Noreturn 声明的函数不会返回. 引入此新的函数修饰符有两个目的:
• 消除编译器对没有return的函数的警告.
• 允许某种只针对不返回函数的优化.
点击(此处)折叠或打开
1. _Noreturn void func();//C11,func never returns
Unicode支持
Unicode标准定义了三种编码格式: UTF-8, UTF-16和UTF-32. 每个都有优点和缺点. 目前, 程序员使用char编码UTF-8, unsigned short或者wchar_t编码UTF-16, unsigned long或者wchar_t编码UTF-32. C11 摒弃了这些hack表示法, 引入了两种新的具有平台独立宽度的数据类型: char16_t和char32_t, 分别用于UTF-16 以及UTF-32(UTF-8像以前一样使用char). C11 也提供u 和 U前缀来表示Unicode字符串, u8前缀表示UTF-8编码的文字. 最后, Unicode转换函数在头文件<uchar.h>中声明.
静态断言
不像#if和#error 预处理指令, 静态断言在后面的翻译阶段当知道表达式的类型的时候被评估的. 因此, 静态断言能够让你捕获预理阶段检测不到的错误.
范围检查的函数
技术报告24731-1现在是C11的不可或缺的一部分, 其定义了标准C库字符串操作函数的边界检查版本. 边界检查版本在原始函数名称后加上_s后缀.
举例来说, strcat()和strncpy() 边界检查版本分别是strcat_s()和strncpy_s(). 大多数边界检查函数都带有一个额外的参数指示了所需处理的缓冲区大小. 其中的许多函数同时也执行额外的运行期检查来检测不同的运行期异常.
让我们来看下两个众所周知的字符串操作函数:
点击(此处)折叠或打开
1. //C11,safe version of strcat
2. errno_t strcat_s(char*restrict s1,rsize_t s1max,constchar*restrict s2);
strcat_s()拷贝不超过s1max字节到s1. 第二个函数strcpy_s()要求s1max应该大于s2 的长度(更准确的说, s1max应该大于strnlen_s(s2, s1max)) 以防越界写操作:
点击(此处)折叠或打开
1. //C11,safe version of strcpy
2. errno_t strcpy_s(char*restrict s1,rsize_t s1max,constchar*restrict s2);
最初, 所有支持边界检查的库都是由微软Visual C++团队开发的. C11实现了一份近似但不完全一致的版本.
移除gets()函数
gets() (在<stdio.h>中声明) 函数从标准输入读取一行并存入调用者提供的缓冲区中. gets()函数不清楚这个缓冲区的实际大小. 恶意程序和攻击者常常利用这个漏洞进行缓冲区溢出攻击. 因此, 在C99标准中gets()函数就被声明为不赞成的. C11干脆整个移除了它并用更安全的版本gets_s()函数取代了它:
点击(此处)折叠或打开
1. char*gets_s(char*restrict buffer,size_t nch);
gets_s()最多从标准输入读取nch个字符.
新fopen()接口
广泛使用的文件I/O函数fopen()在C11中得到了增强.fopen()现在支持新的独占的创建-打开模式(“...x“). 新模式的行为就像POSIX中的O_CREAT|O_EXCL, 通常用于锁文件. “...x” 族模式包括下面几个选项:
• wx 以独占访问创建用于写入的文本文件.
• wbx 以独占访问创建用于写入的二进制文件.
• w+x 以独占访问创建用于更新的文本文件.
• w+bx或wb+x 以独占访问创建用于更新的二进制文件.
使用上面任何一种独占模式打开已存在或者不能被创建的文件都会失败. 否则, 文件会以独占(不共享)访问模式被创建 . 此外, 更安全的fopen()版本fopen_s()也是可用的.
结论
C11试图修复C99中令人沮丧的特性. 它使C99的一些强制特性(变长数组, 复合类型等等) 变成了可选项, 引入了在不同的实现中已经可用的新特性. 并非不太重要的是, C11的设计者们和C++标准委员会紧密合作以确保尽可能保持两种语言的兼容性. 机遇是良好的, 不像它的前任, C11将受到广发的欢迎. 作为奖励, 用C11写的软件将在安全漏洞和恶意软件的攻击面前更加健壮.
See also:
• Understanding The Open Web Stack
• 7 Reasons that Rexx Still Matters
• 14 Ways to Contribute to Open Source without Being a Programming Genius or a Rock Star
Danny Kalev 是通过以色列系统分析师协会认证的系统分析师, 并且是专攻C++的软件工程师. Kalev 写了多本C++的书籍,同时给不同的软件开发者站点投搞C++文章. 他是C++标准委员会的成员, 还获得了通用语言学的硕士学位.
原始鏈接:http://blog.