PDF版 ePub版

# 练习22：栈、作用域和全局

ex22.h

ex22.c

ex22_main.c

main函数实际所在的文件，它会包含另外两个文件，并演示了它们包含的东西以及其它作用域概念。

## ex22.h 和 ex22.c

#ifndef _ex22_h
#define _ex22_h

// makes THE_SIZE in ex22.c available to other .c files
extern int THE_SIZE;

// gets and sets an internal static variable in ex22.c
int get_age();
void set_age(int age);

// updates a static variable that's inside update_ratio
double update_ratio(double ratio);

void print_size();

#endif

#include <stdio.h>
#include "ex22.h"
#include "dbg.h"

int THE_SIZE = 1000;

static int THE_AGE = 37;

int get_age()
{
return THE_AGE;
}

void set_age(int age)
{
THE_AGE = age;
}

double update_ratio(double new_ratio)
{
static double ratio = 1.0;

double old_ratio = ratio;
ratio = new_ratio;

return old_ratio;
}

void print_size()
{
log_info("I think size is: %d", THE_SIZE);
}

extern

static（文件）

static（函数）

THE_SIZE

get_ageset_age

update_ratio

print_size

## ex22_main.c

#include "ex22.h"
#include "dbg.h"

const char *MY_NAME = "Zed A. Shaw";

void scope_demo(int count)
{
log_info("count is: %d", count);

if(count > 10) {
int count = 100;  // BAD! BUGS!

log_info("count in this scope is %d", count);
}

log_info("count is at exit: %d", count);

count = 3000;

log_info("count after assign: %d", count);
}

int main(int argc, char *argv[])
{
// test out THE_AGE accessors
log_info("My name: %s, age: %d", MY_NAME, get_age());

set_age(100);

log_info("My age is now: %d", get_age());

// test out THE_SIZE extern
log_info("THE_SIZE is: %d", THE_SIZE);
print_size();

THE_SIZE = 9;

log_info("THE SIZE is now: %d", THE_SIZE);
print_size();

// test the ratio function static
log_info("Ratio at first: %f", update_ratio(2.0));
log_info("Ratio again: %f", update_ratio(10.0));
log_info("Ratio once more: %f", update_ratio(300.0));

// test the scope demo
int count = 4;
scope_demo(count);
scope_demo(count * 20);

log_info("count after calling scope_demo: %d", count);

return 0;
}

ex22_main.c:4

ex22_main.c:6

ex22_main.c:8

ex22_main.c:10

if语句会开启一个新的作用域区块，并且在其中创建了另一个count变量。这个版本的count变量是一个全新的变量。if语句就好像开启了一个新的“迷你函数”。

ex22_main.c:11

count对于当前区块是局部变量，实际上不同于函数参数列表中的参数。

ex22_main.c:13

ex22_main.c:16

ex22_main.c:18-20

ex22_main.c的剩余部分通过操作和打印变量演示了它们的全部。

ex22_main.c:26

ex22_main.c:27-30

ex22_main.c:33-39

ex22_main.c:42-44

ex22_main.c:46-51

## 你会看到什么

$cc -Wall -g -DNDEBUG -c -o ex22.o ex22.c$ cc -Wall -g -DNDEBUG    ex22_main.c ex22.o   -o ex22_main
\$ ./ex22_main
[INFO] (ex22_main.c:26) My name: Zed A. Shaw, age: 37
[INFO] (ex22_main.c:30) My age is now: 100
[INFO] (ex22_main.c:33) THE_SIZE is: 1000
[INFO] (ex22.c:32) I think size is: 1000
[INFO] (ex22_main.c:38) THE SIZE is now: 9
[INFO] (ex22.c:32) I think size is: 9
[INFO] (ex22_main.c:42) Ratio at first: 1.000000
[INFO] (ex22_main.c:43) Ratio again: 2.000000
[INFO] (ex22_main.c:44) Ratio once more: 10.000000
[INFO] (ex22_main.c:8) count is: 4
[INFO] (ex22_main.c:16) count is at exit: 4
[INFO] (ex22_main.c:20) count after assign: 3000
[INFO] (ex22_main.c:8) count is: 80
[INFO] (ex22_main.c:13) count in this scope is 100
[INFO] (ex22_main.c:16) count is at exit: 80
[INFO] (ex22_main.c:20) count after assign: 3000
[INFO] (ex22_main.c:51) count after calling scope_demo: 4

## 作用域、栈和Bug

• 不要隐藏某个变量，就像上面scope_demo中对count所做的一样。这可能会产生一些隐蔽的bug，你认为你改变了某个变量但实际上没有。
• 避免过多的全局变量，尤其是跨越多个文件。如果必须的话，要使用读写器函数，就像get_age。这并不适用于常量，因为它们是只读的。我是说对于THE_SIZE这种变量，如果你希望别人能够修改它，就应该使用读写器函数。
• 在你不清楚的情况下，应该把它放在堆上。不要依赖于栈的语义，或者指定区域，而是要直接使用malloc创建它。
• 不要使用函数级的静态变量，就像update_ratio。它们并不有用，而且当你想要使你的代码运行在多线程环境时，会有很大的隐患。对于良好的全局变量，它们也非常难于寻找。
• 避免复用函数参数，因为你搞不清楚仅仅想要复用它还是希望修改它的调用者版本。

## 如何使它崩溃

• 试着从ex22_main.c直接访问ex22.c中的你不能访问变量。例如，你能不能获取update_ratio中的ratio？如果你用一个指针指向它会发生什么？
• 移除ex22.hextern声明，来观察会得到什么错误或警告。
• 对不同变量添加static或者const限定符，之后尝试修改它们。

## 附加题

• 研究“值传递”和“引用传递”的差异，并且为二者编写示例。（译者注：C中没有引用传递，你可以搜索“指针传递”。）
• 使用指针来访问原本不能访问的变量。
• 使用Valgrind来观察错误的访问是什么样子。
• 编写一个递归调用并导致栈溢出的函数。如果不知道递归函数是什么的话，试着在scope_demo底部调用scope_demo本身，会形成一种循环。
• 重新编写Makefile使之能够构建这些文件。