# 笨办法学C 练习22：栈、作用域和全局

## 练习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_age``set_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.h``extern` 声明，来观察会得到什么错误或警告。
• 对不同变量添加 `static` 或者 `const` 限定符，之后尝试修改它们。

## 附加题

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