和美国很多著名院校的计算机专业从python开始学习不同,中国的程序员一般是从C语言开始接触编程。曾经困扰笔者一个很久的问题是,C程序的内存分布是怎样的?最近读到一些相关资料,在此做一个简单的总结。
相关概念
在正式开始文章主题之前,先介绍几个核心概念,这些概念有助于对本文的理解。
automatic variable
C/C++中的局部变量(local variable)一般又被称为自动变量(automatic variable),这是因为程序执行到该变量的作用域(lexical scope)时会自动在栈帧(stack frame)中为该变量分配内存空间。
const
const关键字可以用来修饰常量(constant),表示常量的值不会再改变。又分为static const(程序编译期间确保常量的值不发生改变)和dynamic const(程序运行期间确保常量的值不发生改变)。static const示例如下:
const float PI = 3.1415927; // maximal single float precision
const unsigned int MTU = 1500; // Ethernet v2, RFC 894
const unsigned int COLUMNS = 80;
dynamic const示例代码如下:
void f(std::string s) {
const size_t l = s.length();
// ...
}
global variable
全局变量具有一个全局的作用域(global scope),不像C/C++,Java中没有全局变量。C/C++中,并没有global关键字,一个变量如果是在函数外面申明(declaration)的,那么这个变量的作用域就是整个文件(file scope),这个变量就是全局变量。以下是C代码片段:
#include <stdio.h>
static int shared = 3; /* This is the file-scope variable (with internal linkage),
* visible only in this compilation unit.
*/
extern int overShared = 1; /* This one has external linkage (not limited to this
* compilation unit).
*/
int overSharedToo = 2; /* Also external linkage */
static void changeShared(void)
{
shared = 5; /* Reference to the file-scope variable in a function. */
}
static void localShadow(void)
{
int shared; /* local variable that will hide the global of the same name */
shared = 1000; /* This will affect only the local variable and will have no
* effect on the file-scope variable of the same name.
*/
}
static void paramShadow(int shared)
{
shared = -shared; /* This will affect only the parameter and will have no
* effect on the file-scope variable of the same name.
*/
}
int main(void)
{
printf("%d\n", shared); /* Reference to the file-scope variable. */
changeShared();
printf("%d\n", shared);
localShadow();
printf("%d\n", shared);
paramShadow(1);
printf("%d\n", shared);
return 0;
}
注意,int型的shared,overShared和overSharedToo就是全局变量(global variable).如果不使用static关键字,那么以下2种声明变量的方式是等价的:
extern int overShared = 1;
int overSharedToo = 2;
即不用static时,声明一个变量默认就有external linkage,默认就是extern,该变量可以被其它文件访问到。其它文件里的函数如果要使用本文件的全局变量,必须知道本文件中全局变量的数据类型,处理方法是在每个使用该变量的文件中使用extern申明(declaration)该变量,但是只需要在其中一个文件中定义(define)该变量即可。
如果使用static修饰全局变量,该变量只能被本文件中的函数访问到,对其它文件不可见,并且该变量会在程序运行期间一直存在。
static variable
一个静态变量(static variable)是在编译期间分配(allocate)存储空间的,静态变量的生命周期贯穿整个程序运行期间。C中用static修饰的变量是静态变量。
static关键字
在C,C++和Objective-C中,static是一个保留关键字,用于同时控制变量的生命周期(lifetime)和可见性(visibility,通过linkage实现)
bss
如果静态变量(static variable)在声明时没有赋值,那么程序初始化时会把静态变量比特位全部初始化为0,这个变量会被编译器保存到data segment的bss部分。
概念辨析
<1> C中是不是只有使用static修饰的变量才是静态变量(static variable)?
不是。全局变量如果不使用static修饰,并且也不使用const,默认是extern。extern修饰变量也是在编译期间分配存储空间,并且生命周期是整个程序运行期,符合静态变量的定义。
<2> 什么是internal linkage和external linkage?
当你写好实现代码(implementation)文件(.c,.cpp),编译器会产生translation unit,这个就是根据实现代码加上所有引入(#include)的头文件而产生的对象文件(object file,.o)。
内链指的是仅仅位于一个translation unit中。
外链指的是并不仅仅位于一个translation unit内。换言之,就是位于整个程序内,或者说是位于所有translation unit的集合之中。
<3> const修饰变量是不是静态变量(static variable)?
看情况。可以使用extern和static去人为控制一个符号(symbol,包括变量(variable)和常量(constant))的链接属性(linkage,属性可以是internal和external)。注意,extern和static是互斥(mutually exclusive)的。注意,如果没有指指明linkage,那么默认非const(non-const)修饰符号(symbol)的linkage是extern,const修饰符号默认是static(internal)。具体可以参考以下代码注解:
int i; // extern by default
const int ci; // static by default
extern const int eci; // explicitly extern
static int si; // explicitly static
// the same goes for functions (but there are no const functions)
int foo(); // extern by default
static int bar(); // explicitly static
<5> 函数内的局部变量的linkage是什么?
默认是internal。但是可以声明为extern。
<6> 静态变量包括哪些部分?
凡是extern,const,static修饰的都是静态变量。因为全局变量默认就是extern,即使不使用默认的extern,手动加上extern,static或者const,全局变量永远都是静态变量。
C程序内存布局
C程序被加载进内存以后,根据内存分布可以分为4个部分(segment):text segment,data segment,heap,stack,如下图所示:
imagetext segment
text segment,也有叫做code segment,存放的是程序文本,也就是组成程序的机器指令(machine instructions)。
data segment
也有人把data segment叫做static segment,因为这部分存放的都是静态变量(static variable)。
又可以分为2部分,initialized data和uninitialized data(bss)。initialized data部分存放声明并定义过的静态变量。uninitialized data(bss)存放声明但是未定义过的静态变量,程序会把这些静态变量值的各个比特位初始化为0.
heap
堆存放的主要是运行时调用malloc分配存储空间的变量,它会向高地址空间扩展。
stack
栈是由栈帧(stack frame)构成,每一个栈帧保存了一个调用函数的参数和局部变量。栈位于内存的顶部,随着栈逐渐变大,它会向低地址空间扩展。
总结
理解C程序的内存模型并不难,难点主要在于涉及的概念太多,只有先理解了相关概念和易混淆点,才能真正搞懂C程序的内存布局。
网友评论