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