Chinaunix首页 | 论坛 | 博客
  • 博客访问: 328104
  • 博文数量: 57
  • 博客积分: 146
  • 博客等级: 入伍新兵
  • 技术积分: 769
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-29 14:57
文章分类
文章存档

2014年(39)

2013年(13)

2012年(5)

我的朋友

分类: C/C++

2014-06-11 14:58:09

文档:CERT C Programming Language Secure Coding Standard

1.用常量(const)或枚举(enum)来声明不可变值
一般而言,用const来声明不可变值而不是用宏定义。使用const的话,编译器可以检查对象类型(float, double),对象范围等。
对于整型常量,应使用enum(枚举)代替const,可以消除获取整型变量地址可能,同时避免为整型变量分配存储空间。

点击(此处)折叠或打开

  1. int const max = 15;
  2. int a[max]; /* 会报错,invalid declaration outside of a function */
  3. int const *p;
使用enum来代替const

点击(此处)折叠或打开

  1. enum { max = 15 };
  2. int a[max]; /* OK */
  3. int const *p;

2.建议不要重复定义同名变量,在一个域包含另一个域的情况下。

点击(此处)折叠或打开

  1. char msg[100];
  2. void hello_message()
  3. {
  4.     char msg[80] = "Hello";
  5.     strcpy(msg, "Error");
  6. }
修改msg为error_msg.

点击(此处)折叠或打开

  1. char error_msg[100];
  2. void hello_message()
  3. {
  4.     char hello_msg[80] = "Hello";
  5.     strcpy(error_msg, "Error");
  6. }
3.建议使用能可视化清楚表达含义的标识符:
比如:数字1和小写字母l,数字0和大写字母0,很容易引起混淆。

4.声明常量时,const作为声明符号放置在最右边。

点击(此处)折叠或打开

  1. typedef char *NTCS;
  2. const NTCS p;
上述P是指向const char的指针,不是声明一个指向char的const指针。

正确声明:

点击(此处)折叠或打开

  1. typedef char *NTCS;
  2. NTCS const p;

5.建议多个变量分别声明:
我要声明两个char *变量str1,str2。

点击(此处)折叠或打开

  1. char* str1 = 0, str2 = 0
不要认为str1和str2都是char*,实际上str1是char*,str2是char。

 正确方式:

点击(此处)折叠或打开

  1. char *str1 = 0;
  2. char *str2 = 0

6.使用typedef增强代码可读性
声明一个函数指针,其可读性差

点击(此处)折叠或打开

  1. void (*signal(int, void (*)(int)))(int)
使用typedef,增强可读性:

点击(此处)折叠或打开

  1. typedef void fv(int),
  2. typedef void (*pfv)(int);
  3. fv *signal(int, fv *);
  4. pfv signal(int, pfv);

7.建议使用有意义的符号常量来描述文字值,代码中尽量避免使用magic number。

点击(此处)折叠或打开

  1. /* ... */
  2. if (age >= 18) {
  3. /* Take action */
  4. }
  5. else {
  6. /* Take a different action */
  7. }
  8. /* ... */

使用ADULT_AGE来替代18,,更能表达代码意图。

点击(此处)折叠或打开

  1. enum { ADULT_AGE=18 };
  2. /* ... */
  3. if (age >= ADULT_AGE) {
  4. /* Take action */
  5. }
  6. else {
  7. /* Take a different action */
  8. }
  9. /* ... */

8.确保每一个函数都有一个函数原型。
若函数原型不可用,编译器不会对传给函数的参数进行类型检测。C的参数类型检测只发生在编译阶段,不会发生在链接或动态加载阶段。

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. extern char *strchr();
  3. int main(void) {
  4.     char *c = strchr(12, 5);
  5.     printf("Hello %c!\n", *c);
  6.     return 0;
  7. }
上述代码运行会报错。C99上说,extern char *strchr()会阻止编译时的类型检测,导致编译通过运行时报错。

正确做法:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <string.h>
  3. int main(void) {
  4.     char *c = strchr("world", 'w');
  5.     printf("Hello %c!\n", *c);
  6.     return 0;
  7. }

9.若一个函数指针接受的参数个数少于初始化它的函数参数个数,会造成段错误、数据泄露等。

点击(此处)折叠或打开

  1. int add(int x, int y, int z) {
  2.     return x + y + z;
  3. }
  4. int main(int argc, char *argv[]) {
  5.     int (*fn_ptr) (int, int) ;
  6.     int res;
  7.     fn_ptr = &add;
  8.     res = fn_ptr(2, 3); /* incorrect */
  9.     /* ... */
  10.     return 0;
  11. }

fn_ptr(2,3)的参数个数少于add函数(要求三个参数)

点击(此处)折叠或打开

  1. int add(int x, int y, int z) {
  2.     return x + y + z;
  3. }
  4. int main(int argc, char *argv[]) {
  5.     int (*fn_ptr) (int, int, int) ;
  6.     int res;
  7.     fn_ptr = &add;
  8.     res = fn_ptr(2, 3, 4);
  9.     /* ... */
  10.     return 0;
  11. }
10.返回errno的函数返回值类型使用error_t。

点击(此处)折叠或打开

  1. enum { NO_FILE_POS_VALUES = 3 };
  2. int opener(FILE* file, int *width, int *height, int *data_offset) {
  3. int file_w;
  4. int file_h;
  5. int file_o;
  6. fpos_t offset;
  7. if (file == NULL) { return -1; }
  8. if (fgetpos(file, &offset) != 0) { return -1; }
  9. if (fscanf(file, "%i %i %i", &file_w, &file_h, &file_o) != NO_FILE_POS_VALUES) { return -1;
  10. }
  11. if (fsetpos(file, &offset) != 0) { return -1; }
  12. *width = file_w;
  13. *height = file_h;
  14. *data_offset = file_o;
  15. return 0;
  16. }

使用error_t:

点击(此处)折叠或打开

  1. #include <errno.h>
  2. enum { NO_FILE_POS_VALUES = 3 };
  3. errno_t opener(FILE* file, int *width, int *height, int *data_offset) {
  4.     int file_w;
  5.     int file_h;
  6.     int file_o;
  7.     int rc;
  8.     fpos_t offset;
  9.     if (file == NULL) { return EINVAL; }
  10.     if ((rc = fgetpos(file, &offset)) != 0 ) { return rc; }
  11.     if (fscanf(file, "%i %i %i", &file_w, &file_h, &file_o) != NO_FILE_POS_VALUES) { return EIO;
  12.     }
  13.     if ((rc = fsetpos(file, &offset)) != 0 ) { return rc; }
  14.     *width = file_w;
  15.     *height = file_h;
  16.     *data_offset = file_o;
  17.     return 0;
  18. }
11.小心使用可变参数函数

点击(此处)折叠或打开

  1. int average(int first, ...) {
  2.     size_t count = 0;
  3.     int sum = 0;
  4.     int i = first;
  5.     va_list marker;
  6.     va_start(marker, first);
  7.     while (i != -1) {
  8.         sum += i;
  9.         count++;
  10.         i = va_arg(marker, int);
  11.     }
  12.     va_end(marker);
  13.     return(count ? (sum / count) : 0);
  14. }
当遇到-1时,函数停止处理参数。va_start()用来初始化参数表,va_end()用来完成参数表。va_start()和va_end()是成对出现的。

错误的使用:

点击(此处)折叠或打开

  1. int avg = average(1, 4, 6, 4, 1)
由于没有-1这个终止参数,导致函数读取stack的值作为参数直到遇到-1或被中断。

12.可变参数函数的参数是没有定义类型,开发人员要确保传递的参数与对应的参数要一致,除了以下情况:
    a.传递参数为符号整型,相应的参数为无符号整型,他们之间是可以相互表示的。
   b.指向void型的指针,相应的参数类型为字符指针。

类型表示错误:
错误代码,类型不一致:

点击(此处)折叠或打开

  1. char const *error_msg = "Error occurred";
  2. /* ... */
  3. printf("%s:%d", 15, error_msg)
正确书写(开发人员要确保参数类型一致):

点击(此处)折叠或打开

  1. char const *error_msg = "Error occurred";
  2. /* ... */
  3. printf("%d:%s", 15, error_msg)

类型对齐错误:

点击(此处)折叠或打开

  1. long long a = 1;
  2. char msg[128] = "Default message";
  3. /* ... */
  4. printf("%d %s", a, msg)
long long类型用%d来解析,导致数据不完整。
正确:

点击(此处)折叠或打开

  1. long long a = 1;
  2. char msg[128] = "Default message";
  3. /* ... */
  4. printf("%lld %s", a, msg)

阅读(491) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~