【问题标题】:How can we say that "int i" is definition as well as a declaration [duplicate]我们怎么能说“int i”既是定义又是声明[重复]
【发布时间】:2018-07-28 12:48:01
【问题描述】:

这些是 Dennis Ritchie C 编程书籍中的直接语句:

声明是指声明变量的性质但没有分配存储空间的地方。 定义是指变量被创建或分配存储的地方。


问题

“创建变量”和“声明变量”之间究竟有什么区别?

在下面的程序变量j刚刚声明,没有定义,怎么没有编译错误,因为我们将j的地址值赋值给I,而变量j有尚未分配存储空间(编译期间)?

int main()
{
    int *I, j;
    I = &j; 
    printf("%lu", I);
}

output: 140721741346812 

int *I, j是声明还是定义?我得到的是,这是一个定义。但在this link 中,它被声明为声明和定义。

我对链接和 Ritchie 书中给出的两个不同概念感到困惑。

请说明我哪里出错了?是不是将值赋值推迟到运行时,并且在运行时变量j被赋值为垃圾值,因此没有错误?

【问题讨论】:

  • 旁注:printf("%lu", pointer); 是未定义的行为,您需要使用%p 格式说明符打印指针。实际上,在 x64 窗口上,甚至类型大小都不匹配(无符号长:4,指针:8)。
  • int i; 也已经分配了内存,即使没有分配值。仅声明变量(这次是全局变量)的示例是 extern int i;
  • 在标准C11 §6.7 Declarations ¶5 中,它说:声明指定了一组标识符的解释和属性。一个标识符的定义是该标识符的声明:并继续枚举4个特征,其中任何一个都将特定声明纳入定义。因此,所有定义都是声明;有些声明不是定义
  • @Jonathan Letter 这个问题与您提到的问题不重复。这个问题是直接询问这两个术语的定义(声明和定义),而我正在寻找的答案不存在(应该包含更多关于内存分配给变量的确切时间和时间)。此处提供的答案在许多方面与该问题中提供的答案不同。这就是我认为这个问题与那个问题不同的原因。

标签: c declaration definition


【解决方案1】:

声明说明变量的类型(这也有助于确定它需要多少空间)。

定义说明变量的类型,并为变量分配空间。

所以是的,每个定义都是一个声明。

这是一个声明:

extern int j;

在这个声明之后,编译器知道j 的类型是int。在这个声明之后,我们可以说类似

printf("%d\n", j);

我们可以这么说,编译器不会抱怨(至少,它不会马上抱怨),因为至少目前,编译器拥有它需要的所有信息。

但是,如果我们尝试编译这个完整的程序:

#include <stdio.h>

extern int j;

int main()
{
    printf("%d\n", j);
}

我们可能会遇到错误。在我的编译器上,我得到了错误

Undefined symbols: "_j", referenced from: _main
ld: symbol(s) not found

该错误意味着尽管编译器知道 j 是什么,但最终的构建过程 (ld) 在任何地方都没有找到 j 的实际定义。

你说,

这里在上面的程序变量j刚刚声明,没有定义,

不,在您的程序中定义了j

int *i, j是声明还是定义?

这是两个变量的定义。它定义了int 类型的j,以及int * 类型的i,或指向int 的指针。

看起来您已经开始使用指针,当您使用指针时,您必须更仔细地考虑内存分配。 (您可能还需要考虑静态分配和动态分配之间的区别。)

首先要了解的是编译器完全有能力分配内存。编译器一直为变量分配内存。只有当您开始使用指针时,您才需要开始担心内存分配,并自己进行内存分配。

正如我们所说,这条线

int *i;

是一个定义。它声明了一个名为i、类型为pointer-to-int的变量的类型并为其分配空间。

但在这种情况下,我们为指针分配了空间。我们还没有为指针指向的对象分配任何空间。事实上,这个指针还没有指向任何地方:它是未初始化的。

如果我们说

int j;
int *i = &j;

现在指针i 确实指向某个地方:它指向变量j。现在,我们不仅为指针i 分配了内存,还为它分配了指向的内存。

为指向的指针分配内存的另一种方法是进行动态内存分配,通常通过调用malloc。如果我们说

int *i = malloc(sizeof(int));

我们再次为i 及其指向的内容分配了内存。我们让编译器为i分配内存,我们调用malloci动态分配内存指向。

一些图片可能会有所帮助。这是第一段代码后的情况:

   +-----------+
j: |           |
   +-----------+
         ^
         |
   +-----|-----+
i: |     *     |
   +-----------+

这两个盒子都是编译器为我们分配的。

第二段代码后的情况如下:

   +-----------+         +-----------+
i: |     *-------------> |           |
   +-----------+         +-----------+

左边的盒子,i,是编译器分配给我们的,右边未命名的盒子是我们从malloc得到的。

声明呢?是什么

extern int j;

看起来像?我想我会这样画:

j:

有一个符号(名称)j,但它旁边还没有盒子(尽管如果有一个盒子,它的大小和形状应该可以容纳 int)。


在您提出的评论中,

给定int j; int *i=&amp;j;,我们不知道j的内存位置,那么如何将其地址分配给i

我不确定您所说的“我们不知道j 的内存位置”是什么意思。我们可能不知道地址,但编译器知道。编译器会尽一切努力确保在已知位置为j 分配空间。表达式&amp;j 的字面意思是“获取变量j 的已知位置”。因为

int j;

是一个定义,它意味着j将被分配到一个已知位置的空间。


在所有这些中,我一直在说诸如“编译器为...分配内存”之类的东西,但实际上它比这要复杂一些。 实际内存的实际分配可能会在以后发生。

对于局部变量,编译器在堆栈上“分配”变量。它实际上(通常)所做的是在函数的堆栈帧中留出空间。从某种意义上说,局部变量的“地址”是它相对于堆栈帧底部的偏移量。在程序运行之前,变量的实际地址是未知的,函数被调用,并且它的栈帧被创建在堆栈上的特定地址。

对于全局变量,编译器分配我们可能称为暂定定义的内容。 C 被设置为单独编译,这意味着编译器可以在多个源文件上被多次调用,创建多个目标文件,然后由单独的“链接器”程序一起编译成单个可执行。编译器在单个目标文件的数据段中为每个定义的全局变量提供一个暂定地址。 (实际上它可能比这更复杂,如果编译器正在发出汇编语言,而汇编语言是由单独的汇编器汇编的。在这种情况下,编译器发出指令,导致汇编器执行这些暂定分配。)

当链接器将多个目标文件组合在一起时,它也必须将它们的部分暂定数据段组合在一起。此过程涉及为大多数/所有全局变量提供新地址。编译器注意发出额外的信息,称为“重定位信息”,不仅描述每个全局符号的定义,还描述代码中引用该符号的每个位置。这样,当链接器将全局符号移动到它们的最终位置时,它可以返回并调整所有引用——所有对全局函数的调用,以及所有全局变量的地址用&amp; 获取的位置。 (换句话说,是的,链接器正在返回并修改编译器和/或汇编器生成的机器代码。)

在此方案中,外部声明导致目标文件中的信息略有不同。编译器不是临时分配(带有临时地址,通常稍后由链接器调整,以及可能需要重定位的引用列表),编译器发出自己的外部声明版本,告诉链接器它需要在其他一些目标文件中找到这个符号的实际定义。 (它本质上说该符号的暂定地址为 0,以后总会对其进行调整。)但是编译器会发出相同类型的重定位信息,因此当(如果)链接器在另一个目标文件中找到该符号时,它可以在引用它的所有位置填写该地址。

所以如果我们查看编译器为代码发出的低级代码

int *i = &j;

根据j 是本地变量、全局变量(即定义的全局变量)还是外部全局变量,我们会找到三件事之一。

    1234563编译器知道),并将结果地址存储到i
  1. 对于在同一源文件中定义的全局变量,编译器将发出代码以获取j 的暂定地址并将其存储到i。但它也会发出一条重定位记录,指向将j 的地址存储到i 中的指令,以便链接器可以将地址调整为j 的实际最终地址。

  2. 对于外部全局变量,编译器将发出代码以获取未知地址(通常是暂定地址“0”)并将其存储到i。它将发出一条重定位记录,指向将j 的地址存储到i 中的指令,以便链接器可以用j 的实际地址填充它。

(同样,在所有这些中,我一直在提到 堆栈帧,C 不需要,某些架构上的 C 程序也不一定使用。但它们是以体面的方式思考会发生什么,您的机器可能会使用它们。)

【讨论】:

  • 每个定义都是一个声明。但是每个声明都不是定义..对吗?所以,你是说“extern int i”在main外面种植时是一个声明,但在main里面的“int i”既是声明又是定义?
  • 但我不明白,它怎么可能是定义呢?我们只是对编译器说 i 和 j 是两个变量,一个是整数指针类型,另一个是整数类型。但现在还没有分配内存。
  • “声明说明了变量的类型(这也决定了它需要多少空间)。” => 不。 extern char foo[]; - foo 需要多少内存?
  • 在你给出的例子中,int j; int *i=&j;我们不知道 j 的内存位置,那么如何将它的地址分配给 i?
  • @AnttiHaapala 好点——已修复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 2015-05-03
  • 2011-07-16
  • 2014-04-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多