Rule 02. Declarations and Initialization

Rule 02. Declarations and Initialization

DCL30-C. Declare objects with appropriate storage durations

以合理的存储期(storage durations)声明对象。

这条规则的核心是要通过对象的存储期来明确其生命周期(lifetime),不能出现访问生命周期外对象的情况,尤其是在使用指针的时候。

一个错误示例如下,squirrel_away() 函数退出时,local 数组的生命周期结束,此时 ptr 访问了声明周期外的变量,是未定义的行为。

void squirrel_away(char **ptr_param) {
  char local[10];
  /* Initialize array */
  *ptr_param = local;
}
 
void rodent(void) {
  char *ptr;
  squirrel_away(&ptr);
  /* ptr is live but invalid here */
}

改进方式如下,local 具有静态(static)存储期,squirrel_away() 函数返回时,ptr 指向的内容仍是有效的。


char local[10];
  
void squirrel_away(char **ptr_param) {
  /* Initialize array */
  *ptr_param = local;
}
 
void rodent(void) {
  char *ptr;
  squirrel_away(&ptr);
  /* ptr is valid in this scope */
}

对象的存储期可分为:

  • static - 静态存储期
  • thread
  • automatic - 自动存储期
  • allocated

static 就是代码里用 static 修饰的对象,automatic 就是函数内不使用 static 声明的对象,allocated 就是用 malloc 相关函数分配的对象,而 thread 比较特殊,是用 __thread 修饰的对象,表明该对象是该线程专属的。


参考:

DCL31-C. Declare identifiers before using them

使用标识符前必须显式声明他们。

这条规则的核心是使用变量、函数时必须显式地声明他们。C90 标准允许变量和函数的隐式声明,但这在新的 C11 标准中被废弃了,并且新代码也不推荐使用隐式声明,以下有一些例子:

Noncompliant Code Example (Implicit int)

变量声明时如果缺乏类型定义,编译器会尝试隐式声明为 int 类型,例如下述的 foo

extern foo;

foo 可能是其他类型的变量,最终导致错误。

Noncompliant Code Example (Implicit Function Declaration)

当调用一个函数,而该函数并未声明,则 C90 标准会隐式声明一个标识符 extern int identifier();,其函数可以接收任意个数任意类型的参数,且返回值为 int 类型。例如下述例子:

#include <stddef.h>
/* #include <stdlib.h> is missing */
  
int main(void) {
  for (size_t i = 0; i < 100; ++i) {
    /* int malloc() assumed */
    char *ptr = (char *)malloc(0x10000000);
    *ptr = 'a';
  }
  return 0;
}

malloc() 函数的头文件 stdlib.h 并未 include,因此 C90 编译器隐式声明了 int malloc(),如果系统的 int 是 32bit,而指针是 64bit,这就会导致 malloc 返回的 64bit 数据被截断为 32bit,最终导致错误。

Noncompliant Code Example (Implicit Return Type)

当一个函数并未显式声明其返回值类型时,如果函数返回一个整数,C90 编译器隐式声明其返回值为 int 类型。下述例子中 foo() 函数的返回值类型被隐式声明为 int 类型,其返回的 UINT_MAX 被错误的转换成了 -1 。

#include <limits.h>
#include <stdio.h>
  
foo(void) {
  return UINT_MAX;
}
 
int main(void) {
  long long int c = foo();
  printf("%lld\n", c);
  return 0;
}

DCL36-C. Do not declare an identifier with conflicting linkage classifications

不要声明会导致链接冲突的标识符。

标识符的链接属性可分为三类:

  • 外部链接(External linkage):该标识符在整个程序中(所有编译单元和库)代表同一对象或函数。用 extern 声明的标识符具有文件作用域范围且未用 static 修饰的标识符都具有外部链接属性。
  • 内部链接(Internal linkage):该标识符在一个翻译单元(单个文件)内代表同一对象或函数。用 static 修饰的标识符具有内部链接属性。
  • 无链接(No linkage):可以在文件的其他地方声明名称相同的标识符。

多次声明具有不同连接属性的标识符,有时会有未定义的行为,应尽可能避免。

下图中显示了同时声明两个不同链接属性的标识符的最终结果,列为第一次声明,行为第二次声明。

一个简单例子如下,i2i5 都会产生未定义的行为。

int i1 = 10;         /* Definition, external linkage */
static int i2 = 20;  /* Definition, internal linkage */
extern int i3 = 30;  /* Definition, external linkage */
int i4;              /* Tentative definition, external linkage */
static int i5;       /* Tentative definition, internal linkage */
 
int i1;  /* Valid tentative definition */
int i2;  /* Undefined, linkage disagreement with previous */
int i3;  /* Valid tentative definition */
int i4;  /* Valid tentative definition */
int i5;  /* Undefined, linkage disagreement with previous */
 
int main(void) {
  /* ... */
  return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 闲来翻译了一篇官方的JNI Tips,网上看到的翻译版本要么是时间久了不同步了,要么翻译的过于生硬,看得我怀疑自己...
    生活简单些阅读 1,803评论 1 4
  • 线程局部存储(Thread Local Storage,TLS)主要用于在多线程中,存储和维护一些线程相关的数据,...
    waruqi阅读 3,634评论 0 52
  • 参考链接今天看 MJRefresh 源码有一段忽略获的代码,借机整理下相关内容 #pragma在本质上是声明,常用...
    wpf_register阅读 6,864评论 0 2
  • 翻译自:http://llvm.org/docs/LangRef.html#######重启翻译 摘要 这篇文档是...
    呆萌院长阅读 9,448评论 5 4
  • 在iOS开发过程中, 我们可能会碰到一些系统方法弃用, weak、循环引用、不能执行之类的警告。 有代码洁癖的孩子...
    梦翔_d674阅读 2,459评论 0 3