一个例子;
main()
{
int x=123;
}
Run Code Online (Sandbox Code Playgroud)
所以,x是一个int类型变量,并分配2个字节int(当前假设,这可能在机器方面有所不同.)
让我们说,地址2000和2001分配x.那么,如何123使用这些地址存储数据?
我是C的初学者,所以简单的语言会有所帮助.
我指的是E Balagurusamy [Mc Graw Hill Education]的"计算基础与C程序设计".
当我考虑在C中存储变量时,我主要考虑的是与机器无关的盒子.所以给定
int x = 123;
Run Code Online (Sandbox Code Playgroud)
我的第一个想法是它看起来像这样:
+-----------+
x: | 123 |
+-----------+
Run Code Online (Sandbox Code Playgroud)
由于这是一个局部变量,我知道这个小盒子在堆栈上.(下面有更多内容.)
现在,您询问了各个字节,并设想了一个int从地址开始的双字节2000.所以这里的内容将更详细.为了强调单个字节的内容,我将切换到十六进制:
+------+
x: 2000: | 0x7b |
+------+
2001: | 0x00 |
+------+
Run Code Online (Sandbox Code Playgroud)
您可能已经想到了这一点,但该数字0x7b是十六进制表示的一部分123.十进制数123具有十六进制表示0x007b.这确实假设一个两字节的整数,虽然值得注意的是,这些天你可能使用的大多数机器将使用四个字节.我还展示了"小端"存储的情况,这是大多数机器今天使用的惯例.较低编号的字节是最不重要的字节.
由于123实际上只是一个7位数,它只占两个字节中的一个,另一个为零.为了确定我们理解两个字节是如何布局的,假设我们为x分配一个新值:
x = 12345;
Run Code Online (Sandbox Code Playgroud)
十六进制表示12345为0x3039,所以内存中的图片更改为:
+------+
x: 2000: | 0x39 |
+------+
2001: | 0x30 |
+------+
Run Code Online (Sandbox Code Playgroud)
最后,为了比较,假设我说
long int y = 305419896;
Run Code Online (Sandbox Code Playgroud)
并且假设这是在具有相反的大端字节顺序的机器上.假设long ints是四个字节,并假设编译器选择放在y地址3000.那个随机数字305419896具有十六进制表示0x12345678,因此内存中的情况(再次,假设大端字节顺序)将如下所示:
+------+
y: 3000: | 0x12 |
+------+
3001: | 0x34 |
+------+
3002: | 0x56 |
+------+
3003: | 0x78 |
+------+
Run Code Online (Sandbox Code Playgroud)
对于big-endian存储,低位地址包含最重要的字节,这意味着该数字在内存中从左向右(此处从上到下)读取.但正如我所说,你今天可能使用的大多数机器都是小端的.
正如我所提到的,因为x在你的例子中是一个局部变量,它通常会存储在函数调用堆栈中.(正如一位评论者所指出的,不能保证C甚至有一个堆栈,但大多数都是这样,所以让我们继续这个假设.)要看到局部变量和全局变量之间的区别,还要说明其他几种数据类型是怎样的存储,让我们看一个稍微大一点的例子,让我们扩展我们的范围来想象所有的内存.假设我写
int g = 456;
char s[] = "hello";
int main() {
int x = 123;
char s2[] = "world";
char *p = s;
}
Run Code Online (Sandbox Code Playgroud)
通常,全局变量存储在内存的下半部分,而堆栈存储在"顶部",并且向下增长.所以我们可以想象我们的计算机内存看起来像这样.正如你所看到的,我已经颠倒了我在前面图片中使用的惯例.在这张图片中,内存地址上运行了该页面.(此外,内存地址现在也是十六进制的,我正在删除0x符号.此外,我将回到little-endian字节顺序,但保留了16位机器的概念.此外,我要去将字符值显示为自身,而不是十六进制.此外,我将未知字节显示为??.)
+------+
ffec: | ?? |
+------+
ffeb: | 00 |
+------+
x: ffea: | 7b |
+------+
ffe9: | 00 |
+------+
ffe8: | 'd' |
+------+
ffe7: | 'l' |
+------+
ffe6: | 'r' |
+------+
ffe5: | 'o' |
+------+
s2: ffe4: | 'w' |
+------+
ffe3: | 02 |
+------+
p: ffe2: | 80 |
+------+
ffe1: | ?? |
+------+
.
.
.
+------+
0282: | ?? |
+------+
0281: | 00 |
+------+
0280: | 'o' |
+------+
0283: | 'l' |
+------+
0282: | 'l' |
+------+
0281: | 'e' |
+------+
s: 0280: | 'h' |
+------+
0283: | 01 |
+------+
g: 0282: | c8 |
+------+
0281: | ?? |
+------+
Run Code Online (Sandbox Code Playgroud)
当然,这是一个巨大的简化,但它应该给你一个基本的想法,我仍然觉得以这种方式思考它是有用的,尽管评论者正忙着讨论的所有深奥的可能的复杂性.下一步可能是展示malloc'内存的外观,以及当我们在堆栈上激活了多个函数调用时的外观,但是这个答案太长了,所以我们将把它保存到另一天.
您不应该关心数据是如何存储的,但您应该考虑您的程序的行为(因此请多考虑语义).
你的代码错了.你至少需要拥有int main(void)
你的编译器可以:
完全忘记变量.在您的示例中,x没有可观察到的影响,编译器可以删除int x = 123;
仅将变量存储在某个处理器寄存器中(因此变量不在内存中并且没有内存地址)
将变量放在调用堆栈的当前堆栈帧的某个槽中(或者可能在内存中的其他位置).
等......(包括之前案例的一些组合)
当然,如果你添加一些可观察到的副作用(也许是printf("x=%d\n", x);因为你的后声明int x = 123; 的定义自动变量),编译器将处理该变量非常不同.
C标准(读n1570)不仅指定(英文)语法,还指定C编程语言的语义,即程序的可观察行为.一个重要且棘手的概念是未定义的行为(UB;你问题中的玩具程序没有任何UB,但你很快就会编写有一些UB的错误程序,你需要学习如何避免UB).被吓得 UB的.
一些副作用是不相关的.例如,加热处理器.
当前的实现(编译器和系统)可以帮助您理解程序的行为.我建议使用所有警告和调试信息进行编译(gcc -Wall -Wextra -g使用GCC)并使用gdb调试器.