C编程时数据是如何存储的?

How is data stored during C programming?

一个例子;

main()
{
    int x=123;
}

所以,x 是一个 int 类型变量,为 int 分配了 2 个字节(当前假设,这可能与机器不同。)

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

我指的是 E Balagurusamy [Mc Graw Hill Education] 的 "Computing Fundamentals & C Programming"。

当我想到 C 中的变量存储时,我主要想到 machine-independent 个盒子。所以给出

int x = 123;

我的第一个想法就是它看起来像这样:

   +-----------+
x: |    123    |
   +-----------+

因为这是一个局部变量,所以我知道这个小盒子在堆栈上。 (更多内容见下文。)

现在,您询问了各个字节,并想象了一个从地址 2000 开始的 two-byte int。所以这里是更详细的样子。为了强调各个字节的内容,我将切换为十六进制:

         +------+
x: 2000: | 0x7b |
         +------+
   2001: | 0x00 |
         +------+

您可能已经弄明白了,但是数字 0x7b123 的十六进制表示形式的一部分。十进制数 123 具有十六进制表示 0x007b。这确实假定了一个 two-byte 整数,尽管值得注意的是,如今,您可能使用的大多数机器都将使用四个字节。我还展示了 "little endian" 存储的情况,这是当今大多数机器使用的惯例。 lower-numbered 字节是 least-significant 字节。

由于123实际上只是一个7位数字,所以它只占两个字节中的一个,另一个为零。为确保我们理解这两个字节的布局方式,假设我们为 x:

分配了一个新值
x = 12345;

12345的十六进制表示为0x3039,所以内存中的图片变为:

         +------+
x: 2000: | 0x39 |
         +------+
   2001: | 0x30 |
         +------+

最后,为了对比,假设我说

long int y = 305419896;

并假设这是在具有相反 big-endian 字节顺序的机器上。假设 long int 是四个字节,并且假设编译器选择将 y 放在地址 3000 处。random-looking 数字 305419896 具有十六进制表示 0x12345678,所以内存中的情况(同样,假设 big-endian 字节顺序)看起来像这样:

         +------+
y: 3000: | 0x12 |
         +------+
   3001: | 0x34 |
         +------+
   3002: | 0x56 |
         +------+
   3003: | 0x78 |
         +------+

用big-endian存储,low-order地址包含most-significant字节,意思是这个数在内存中读到left-to-right(这里是top-to-bottom)。但正如我所说,您今天可能使用的大多数机器是 little-endian.

正如我所提到的,由于您示例中的 x 是局部变量,因此它通常存储在函数调用堆栈中。 (正如一位评论者所指出的,不能保证 C 甚至有一个堆栈,但大多数都有,所以让我们继续这个假设。)查看局部变量和全局变量之间的区别,并显示其他几个数据类型是如何存储,让我们看一个稍微大一点的例子,让我们扩大我们的范围来想象所有的内存。假设我写

int g = 456;
char s[] = "hello";

int main() {
    int x = 123;
    char s2[] = "world";
    char *p = s;
}

一般情况下,全局变量存储在内存的低位部分,而堆栈存储"at the top",并向下增长。所以我们可以想象我们的计算机的内存看起来像这样。正如您将看到的,我颠倒了我在前面图片中使用的约定。在这张图片中,内存地址 运行 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 的内存看起来如何,以及当我们在堆栈上激活多个函数调用时的样子,但这个答案太长了,所以我们将把它保存到改天。

你不应该关心数据是如何存储的,但你应该考虑你的程序的行为方式(所以多考虑semantics)。

您的代码有误。你至少需要 int main(void)

注意可能的 optimizations by the compiler, and of the "as-if" rule

您的编译器可以:

  • 完全忘记变量。在您的示例中,x 没有可观察到的效果,编译器可以删除 int x = 123;

  • 仅将变量存储在一些processor register中(因此变量不在内存中并且没有内存地址)

  • 将变量放入 call stack 当前堆栈帧的某个槽中(或者可能在内存中的其他位置)。

  • 等...(包括以前案例的一些混合)

当然,如果您添加一些可观察的 side-effect (perhaps a printf("x=%d\n", x); as a statement after your int x = 123; definition of an automatic variable),编译器将以非常不同的方式处理该变量。

C标准(阅读n1570) specifies (in English) not only the syntax, but also the semantics of the C programming language, that is the observable behavior of programs. An important and tricky notion is that of undefined behavior (UB; the toy program in your question don't have any UB, but you'll soon code buggy programs which have some UB, and you need to learn how to avoid UB). Be scared of UB.

有些 side-effects 不相关。例如,加热处理器。

当前的实现(编译器和系统)可以帮助您理解程序的行为。我建议使用所有警告和调试信息进行编译(因此 gcc -Wall -Wextra -gGCC) and using the gdb debugger