在C编程期间如何存储数据?

Sah*_*hil 1 c int storage

一个例子;

main()
{
    int x=123;
}
Run Code Online (Sandbox Code Playgroud)

所以,x是一个int类型变量,并分配2个字节int(当前假设,这可能在机器方面有所不同.)

让我们说,地址20002001分配x.那么,如何123使用这些地址存储数据?
我是C的初学者,所以简单的语言会有所帮助.

我指的是E Balagurusamy [Mc Graw Hill Education]的"计算基础与C程序设计".

Ste*_*mit 9

当我考虑在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)

十六进制表示123450x3039,所以内存中的图片更改为:

         +------+
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'内存的外观,以及当我们在堆栈上激活了多个函数调用时的外观,但是这个答案太长了,所以我们将把它保存到另一天.


Bas*_*tch 6

您不应该关心数据是如何存储的,但您应该考虑您的程序的行为(因此请多考虑语义).

你的代码错了.你至少需要拥有int main(void)

请注意编译器可能的优化以及"as-if"规则.

你的编译器可以:

  • 完全忘记变量.在您的示例中,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调试器.