C 语言

extern 作用

extern 影响程序的链接过程,表示对变量或函数的声明,真正的定义在其他地方。

  • 函数默认是外部链接(extern)
  • 而变量而言,文件级别的定义为 extern,block 级别的为内部。

在 C 中声明全局变量的方式:

  • 在头文件中使用 extern 声明( int i 这种形式称为 tentative definition
  • 在一个 C 文件中定义,
  • 其他文件直接引用头文件即可
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;              /* not legal, linkage disagreement with previous */
int i3;              /* valid tentative definition */
int i4;              /* valid tentative definition */
int i5;              /* not legal, linkage disagreement with previous */

// 在 link 阶段,变量默认是 external 的,使用 static 可以变成 internal 的
// 任何文件级别的变量,都按照 static memory model 分配,不管有没有 extern/static 修饰
static int a = 1;

// 在 link 阶段,函数默认是外部链接,使用 static 可以变成内部链接
int add(int x, int y) {
  // block 级别的变量在 link 阶段默认是 internal 的,可以使用 extern 改成外部链接,但是很少这么做
  // 内存模型是 auto 的,使用 static 会改变内存模型为 static
  int a = 10;
  return x + y + a;
}

const

解读声明的诀窍是从右向左阅读。具体如下:

  • int const A constant integer
  • int const * A (variable) pointer to a constant integer
  • int * const A constant pointer to a (variable) integer
  • int * const * A pointer to a constant pointer to an integer
  • int const * * A pointer to a pointer to a constant integer
  • int const * const * A pointer to a constant pointer to a constant integer

可以看出, const 总是修饰它左边的内容,与 * 的规律是一致的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>

extern int a;

// const 只能限定 b,foo 可以修改其指向的元素
void set_elmt(int *foo, int const *b) {
  foo[0] = 3;
  // error: read-only variable is not assignable
  /* b[1] = 4; */
}

int main() {
  printf("a = %d\n", a);

  int foo[10] = {};
  int const *b = foo;
  set_elmt(foo, b);
  printf("a = %d\n", *foo);
}

局限性

下面演示说明:对于 const 的 struct,其内部的成员是可以被修改的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <assert.h>
#include <stdio.h>
#include <stdlib.h> //assert

typedef struct {
  int *counter1, *counter2;
} counter_s;

void check_counter(int *ctr){ assert(*ctr !=0); }

double ratio(counter_s const *in){
  check_counter(in->counter2);
  return *in->counter1/(*in->counter2+0.0);
}

int main(){
  counter_s cc = {
    // Designated Initializers
    .counter1=malloc(sizeof(int)),
    .counter2=malloc(sizeof(int))
  };
  *cc.counter1 = *cc.counter2 = 1;
  printf("ratio = %f\n",ratio(&cc));

}

指针与数组的区别

数组名和指针之间存在一个重要区别:

  • 指针是一个变量,因此像 pa=apa++ 这样的操作是合法的。
  • 但数组名并不是一个变量,因此类似 a=paa++ 这样的用法是非法的。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int a[10];
int *pa = &a[0];
// 等价于
int *pa = a;
// valid
pa = a
pa ++

// invalid
a = pa
a++
1
2
char amessage[] = "now is the time"; /* an array */
char *pmessage = "now is the time"; /* a pointer */
  • 可以通过 amessage 修改字符串内容,但是不能修改 amessage 的指向
  • 可以修改 pmessage 的指向,但是不能修改字符串的内容
    字符指针与数组的区别

    字符指针与数组的区别

如何解读 C 声明

变量声明的语法模仿了变量可能出现在表达式中的语法结构,从而使两者显得更加一致和自然。

The syntax of the declaration for a variable mimics the syntax of expressions in which the variable might appear.

1
double *dp, atof(char *);

上面的声明可以解读为:

  • 在表达式 *dpatof(s) 都会产生一个 double 类型的值。
  • atof 的参数是一个指向 char 的指针
1
int *A[100];

这意味着我们可以使用 *A[i] 表达式来获取一个整数值,所以 A :

  1. 必须是一个数组(因为它可以用下标访问)
  2. 数组的元素必须是指针(因为它可以被解引用)

所有 A 的类型是:数组(100 个元素),元素类型是指向整数的指针。

1
int (*A)[100];

类似的,由于我们可以使用 (*A)[0] 表达式来获取一个整数值,所以 A:

  • 必须是一个指针(因为它可以被解引用)
  • 指针指向的内容必须是一个数组(因为它可以用下标访问)

所以 A 的类型是:指向一个包含 100 个整数的数组的指针。

参考:c - What is "Syntax of Expression" - Stack Overflow