7

我试图理解 C 中范围的确切含义。我能理解的是范围仅限于编译时间。例如,如果您从其他函数访问局部变量。这将导致编译时错误。另一方面,以下程序可以正常工作。这意味着 C 具有平坦的内存模型,并且可以在运行时访问任何内容。C 书籍将作用域与生命周期和变量可见性联系起来,我发现它很混乱。我认为所有这些术语仅在编译时才有意义。有人可以照亮它吗?

#include "stdio.h"

int *ptr;

int func(void)
{
  /** abc is a local variable **/
  int abc = 132;
  ptr = &abc;
  return 0;
}

int func1(void)
{

  /** although scope of abc is over still I can change the value in the address  of abc **/
  *ptr = 200;
  printf("the value of abc=%d\r\n",*ptr);

}

int main(void)
{
   func();
   func1();
   return 0;
}

结果:abc=200的值

用简单的话来说,范围是什么意思?它是在运行时还是编译时出现的?正如我们所见,我们可以在运行时访问任何东西。但是,如果我们不遵守规则,那么我们就会得到编译错误。例如,另一个函数中的局部变量引用。编译器会抛出一个错误,说“变量未定义......”。

我可以说以下关于变量的内容吗?

1) Scope attribute comes under compile time.
2) Lifetime attribute comes under run-time.
3) Visibility attribute comes under compile-time
4

7 回答 7

8

是的,C 的内存模型允许您轻松访问任何内容,因此您可以实际执行上述操作并查看“有趣”的结果。

但是,您在此处所做的操作被 C 标准指定为未定义行为(UB)。这意味着实际上任何事情都可能发生。这可能是您所期望的,也可能不是。

请注意,您没有访问“局部变量”,因为在您进行访问时func已经返回,因此其局部变量的生命周期已经到期。您访问的一个“恰好”具有有趣值的内存区域。如果您func1从内部调用,func那么行为将是明确定义的。

还有一些注意事项:

范围绝对是一个仅编译时的概念;名称(变量、标识符等)的范围是程序代码的子集,其中该名称被编译器识别。

这与变量的生命周期非常不同,变量的生命周期在一般情况下与范围无关,将两者混为一谈是一个常见的错误。局部变量的生命周期和范围确实是交织在一起的,但并非所有事情都是如此。

于 2013-09-04T11:41:32.067 回答
1

就范围而言,最简单的方法是将 C 编译成一系列寄存器和内存操作,诸如块、for 循环、if 语句、结构等结构在编译之外没有任何意义,它们只是抽象让您作为程序员保持理智。

就示例和记忆而言,这是我试图解释它的尝试

正如每个人所说,您正在使用某些编译器特定的实现,按照标准,未定义的操作。要理解它是如何工作的,你可以把你正在编写的程序想象成有两个内存,堆和栈。例如char *foo = malloc(50);在堆上分配内存并在char foo[] = "Foo"堆栈上分配。堆栈是记住你在做什么的内存,它包含一长串堆栈帧,每个函数调用都会添加一个帧,每次返回都会弹出一个帧。堆是另一种内存。

为了说明这一点,我们有这个程序:

int *ptr;
int func() {
  int abc = 123;
  ptr = &abc;
  return 0;
}

int func1() {
  int def;
  printf("func1() :: *abc=%i\n", *ptr);
  def = 200;
  return 0;
}

int main() {
  func();
  printf("main() :: *ptr=%i\n", *ptr);
  func1();
  printf("main() :: *ptr=%i\n", *ptr);
}

堆栈上将发生以下情况(-未定义/未使用的内存,>左侧是您的程序当前正在执行的位置,X是数据):

|-----|
|-----|
|-----|

当我们进入main()时,它将堆栈帧推入堆栈:

 |-----|
 |-----|
>|XXXXX| <- This is where all memory needed to execute main() is.

然后你调用func()which ,在需要执行的内存中,包含 integer 123

 |-----|
>|XX123| <- This is the stack frame for func()
 |XXXXX| <- Still the stack frame for main()

func()将全局指针设置*ptr为堆栈上整数的地址。这意味着当func()返回时(并且内存没有被清除,因为这会浪费 CPU 周期),该值仍然存在

 |-----|
 |--123|
>|XXXXX| <- main()

*ptr并且在调用下一个函数之前仍然可以被引用

 |-----|
>|XXXXX| <- This is the stack frame for func1()
 |XXXXX|

现在*ptr似乎有一些随机值......但您仍然可以访问内存位置并更改它。(如果func()and func1()each 在其范围内仅定义一个本地整数,则很可能也*ptr将指向该整数func1()

奖金

我还没有测试过这个程序,但我认为它会打印出这样的东西:

main() :: *ptr=123
func1() :: *ptr=<some random values>
main() :: *ptr=<possibly 200, could be something else>
于 2013-09-04T16:11:54.110 回答
1

范围是什么意思?

变量的范围是可以引用变量的文本部分。局部变量具有块作用域:从其声明点到封闭函数体的末尾都是可见的。
它与变量的生命周期无关。它是存储持续时间,它讲述了变量的生命周期。

它是在运行时还是编译时出现的?

它在编译时和链接时出现。当程序试图访问其块之外的局部变量时,编译器会给您一个关于该未声明变量的错误(它是其块的本地变量)。
这个例子将更好地解释它:

#include <stdio.h>

void userlocal(void);

int main()
{
    int a= 2;

    printf("local a in outer scope of main is %d\n",a);

    userlocal();

    printf("local a in scope of userlocal is %d\n",b); // This will give error at compile time
    return 0;

}

void userlocal(void)
{
    int b = 20;
    printf("local a in scope of userlocal is %d\n",b);
}

输出:

[Error] 'b' undeclared (first use in this function)  

我可以说以下关于变量的内容吗?

是的,你可以说。

于 2013-09-04T11:43:18.173 回答
1

虽然理论上它“只是 UB”,但实际上它要求实际失败。的位置abc(在我知道的每个实现中)在堆栈上的某个位置。由于您已经离开了初始块,然后进入了其他块,因此很可能其他东西会占用该内存位置。你要覆盖哪个。

于 2013-09-04T11:52:19.990 回答
0

它是未定义的,你不应该相信价值。对您的代码 (MSVC 2013) 进行一些小改动,就会有惊喜。

int func1()
{   
    /** although scope of abc is over still I can change the value in the address  of abc **/
    printf("the value of abc=%d\r\n", *ptr);
    *ptr = 200;
    printf("the value of abc=%d\r\n", *ptr);
    return 0;
}

int main()
{
    func();
    printf("the value of abc=%d\r\n", *ptr);
    func1();
}

输出在我的电脑上。

abc的值=132
abc的值=1519668 abc
的值=200

于 2013-09-04T12:33:15.953 回答
0

你说得对,C 有一个平坦的内存模型。并允许访问任何内存区域。这ptr是一个指向int. 所以它指向一个可以存储和检索整数的地址。这将导致undefined behaviour不同类型的场景。

于 2013-09-04T11:42:00.300 回答
0

作用域是一个编译时属性,当引用一个变量是有效的或者当它是可见的时处理,这storage duration与一个对象的生命周期或生命周期不同,它告诉什么时候访问与变量关联的内存是有效的。

在这种情况下abc具有自动存储持续时间,这意味着它的持续时间会扩展到它所在的块,在这种情况下是函数func,并且尝试访问与abc该块外部关联的内存是未定义的行为,它可能看起来有效,但结果不能靠。

从草案 C99 标准部分6.2.4 存储对象的持续时间段落讨论了各种存储持续时间:staticautomatic. In 解释说static变量生命周期是程序的整个执行过程,在第 5 段中它说:

对于这样一个没有可变长度数组类型的对象,它的生命周期从进入与其关联的块开始,直到该块的执行以任何方式结束。[..]

因此,自动变量 life 是它包含的块,第 2 段说:

对象的生命周期是程序执行期间保证为其保留存储的部分。一个对象存在,具有一个常量地址,25) 并在其整个生命周期中保留其最后存储的值。26)如果一个对象在其生命周期之外被引用,则行为是未定义的。当指针指向的对象到达其生命周期的末尾时,指针的值变得不确定。

因此,在其保证生命周期之外引用一个对象是未定义的行为。

于 2013-09-04T11:45:41.810 回答