C中的变量类型以及谁跟踪它

C中的变量类型以及谁跟踪它,c,cs50,C,Cs50,我正在哈佛大学学习MOOC课程。在第一堂课中,我们学习了不同数据类型的变量:int,char,等等 我所理解的是,该命令(例如,在main函数中)inta=5在堆栈上保留大量字节(大部分为4字节)内存,并将表示5的零和一序列放在堆栈中 相同的零和一序列也可能意味着某个字符。因此,有人需要跟踪这样一个事实,即为a保留的内存中的0和1序列将被读取为整数(而不是字符) 问题是谁会跟踪它?通过在内存中的这个位置贴上一个标签,上面写着“嘿,你在这4个字节中找到的任何东西都可以读作一个整数”来控制计算机的内

我正在哈佛大学学习MOOC课程。在第一堂课中,我们学习了不同数据类型的变量:
int
char
,等等

我所理解的是,该命令(例如,在
main
函数中)
inta=5
在堆栈上保留大量字节(大部分为4字节)内存,并将表示
5
的零和一序列放在堆栈中

相同的零和一序列也可能意味着某个字符。因此,有人需要跟踪这样一个事实,即为
a
保留的内存中的0和1序列将被读取为整数(而不是字符)

问题是谁会跟踪它?通过在内存中的这个位置贴上一个标签,上面写着“嘿,你在这4个字节中找到的任何东西都可以读作一个整数”来控制计算机的内存?或者C编译器,它知道(查看
a
的类型
int
)当我的代码要求它用
a
的值做某事(更准确地说,生成一个机器代码做某事)时,它需要将这个值作为整数处理


我非常感谢为C初学者量身定做的答案

对于主要部分,C编译器负责跟踪

在编译过程中,编译器构建一个称为解析树的大型数据结构。它还跟踪所有变量、函数、类型等。。。具有名称(即标识符)的所有内容;这称为符号表

解析树和符号表的节点都有一个记录类型的条目。他们跟踪所有的类型

主要使用这两种数据结构,编译器可以检查代码是否违反类型规则。它允许编译器在您使用不兼容的值或变量名时发出警告

C确实允许类型之间的隐式对话。例如,您可以将
int
分配给
double
。但在内存中,对于相同的值,这些是完全不同的位模式

在编译过程的早期(更高抽象级别)阶段,编译器还没有处理位模式(或太多),而是在更高级别上进行转换和检查

但是在汇编代码生成过程中,编译器需要最终以位的形式将其全部计算出来。因此,对于
int
double
的转换:

int    i = 5;
double d = i; // Conversion.
编译器将生成代码以进行此转换

然而,在C语言中,很容易出错,把事情搞得一团糟。这是因为C不是一种非常强类型的语言,而且非常灵活。因此,程序员也需要意识到这一点


因为C在编译后不再跟踪类型,所以当程序运行时,在执行一些错误后,程序通常可以使用错误的数据继续运行。如果幸运的是程序崩溃了,那么错误消息就不是(非常)有用了。

您有一个堆栈指针,它给出了内存中最顶层堆栈帧的绝对偏移量

对于给定的执行范围,编译器知道相对于堆栈指针的变量,并在堆栈指针的偏移量上发出对这些变量的访问。因此,主要是编译器映射变量,但应用此映射的是处理器


您可以很容易地编写程序来计算或记住一个曾经有效的内存地址,或者刚好在有效区域之外。编译器不会阻止您这样做,只有具有引用计数和严格边界检查的高级语言才能在运行时执行此操作。

对于C语言,它是编译器

在运行时,堆栈上只有32位=4字节

你问“通过在这个地方贴标签来获取计算机的内存…”:这是不可能的(在当前的计算机架构下——感谢@Ivan的提示)。内存本身只有8位(0或1)ber字节。内存中没有任何地方可以用任何附加信息标记内存单元


还有其他一些语言(例如LISP,某种程度上还有Java和C#)将整数存储为32位数字加上一些位或字节的组合,这些位或字节包含一些位编码的标记,这里我们有一个整数。因此,32位整数需要6个字节。但对于C,情况并非如此。您需要源代码的知识来正确解释内存中找到的位——它们本身无法解释。还有一些特殊的体系结构支持硬件中的标记。

在C中,内存是非类型化的;没有超出其价值的信息存储在那里。所有类型信息都是在编译时根据表达式的类型(变量名、值计算、指针解引用等)计算的。此计算取决于程序员通过声明(也在头中)或强制转换提供的信息。如果该信息是错误的,例如,因为函数原型的参数被声明为错误,则所有赌注都是无效的。编译器会警告或防止同一“翻译单元”(带有标题的文件)中的错误声明,但在翻译单元之间没有(或不太多?)保护。这就是C有标题的原因之一:它们在翻译单元之间共享公共类型信息


C++保留了这一思想,但还为多态类型提供了运行时类型信息(与编译时类型信息相反)。很明显,每个多态对象都必须在某个地方携带额外的信息(但不一定靠近数据)。但这是C++,而不是C.</P> < P>编译器在翻译过程中保持所有类型信息的跟踪,并生成合适的机器代码来处理不同类型或大小的数据。p> 让我们看一下下面的代码:

#include <stdio.h>

int main( void )
{
  long long x, y, z;

  x = 5;
  y = 6;
  z = x + y;

  printf( "x = %ld, y = %ld, z = %ld\n", x, y, z );
  return 0;
}
movq
是将值移动到64位字(“四字”)的助记符<
    movq    $5, -24(%rbp)
    movq    $6, -16(%rbp)
    movq    -16(%rbp), %rax
    addq    -24(%rbp), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rcx
    movq    -16(%rbp), %rdx
    movq    -24(%rbp), %rsi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    movl    $0, %eax
    leave
    ret
#include <stdio.h>

int main( void )
{
  short x, y, z;

  x = 5;
  y = 6;
  z = x + y;

  printf( "x = %hd, y = %hd, z = %hd\n", x, y, z );
  return 0;
}
    movw    $5, -6(%rbp)
    movw    $6, -4(%rbp)
    movzwl  -6(%rbp), %edx
    movzwl  -4(%rbp), %eax
    leal    (%rdx,%rax), %eax
    movw    %ax, -2(%rbp)
    movswl  -2(%rbp),%ecx
    movswl  -4(%rbp),%edx
    movswl  -6(%rbp),%esi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    movl    $0, %eax
    leave
    ret
#include <stdio.h>

int main( void )
{
  double x, y, z;

  x = 5;
  y = 6;
  z = x + y;

  printf( "x = %f, y = %f, z = %f\n", x, y, z );
  return 0;
}
    movabsq $4617315517961601024, %rax
    movq    %rax, -24(%rbp)
    movabsq $4618441417868443648, %rax
    movq    %rax, -16(%rbp)
    movsd   -24(%rbp), %xmm0
    addsd   -16(%rbp), %xmm0
    movsd   %xmm0, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    -16(%rbp), %rdx
    movq    -24(%rbp), %rcx
    movq    %rax, -40(%rbp)
    movsd   -40(%rbp), %xmm2
    movq    %rdx, -40(%rbp)
    movsd   -40(%rbp), %xmm1
    movq    %rcx, -40(%rbp)
    movsd   -40(%rbp), %xmm0
    movl    $.LC2, %edi
    movl    $3, %eax
    call    printf
    movl    $0, %eax
    leave
    ret