为什么设置一个取消引用指针等于一个原始的非法?

时间:2014-01-16 01:15:07

标签: c pointers

为什么设置解除引用指针的值会引发分段错误11?为了明确我的意思,请查看以下代码:

#include <stdio.h>

int *ptr;
*ptr = 2;

int main(){
  printf("%d\n", *ptr);
  return 0;
}

我认为* ptr = 2会将指针ptr指向的rvalue设置为2.是不是这样?对于那些c专家程序员来说,我很抱歉,这真的很容易/很明显。

如果该值具有内存地址,我们是否只允许将解除引用的指针(即* ptr)设置为值?即喜欢做:

int k = 7;
int *ptr = k;

然后:

*ptr = 2;

4 个答案:

答案 0 :(得分:3)

这里的问题是ptr没有指向分配的空间。请参阅以下内容:

#include <stdio.h>
#include <stdlib.h> 

int main(void){

  // Create a pointer to an integer.
  // This pointer will point to some random (likely unallocated) memory address.
  // Trying set the value at this memory address will almost certainly cause a segfault.
  int *ptr;

  // Create a new integer on the heap, and assign its address to ptr.
  // Don't forget to call free() on it later!
  ptr = malloc(sizeof(*ptr)); 

  // Alternatively, we could create a new integer on the stack, and have
  // ptr point to this.
  int value;
  ptr = &value;

  // Set the value of our new integer to 2.
  *ptr = 2;

  // Print out the value at our now properly set integer.
  printf("%d\n", *ptr);

  return 0;
} 

答案 1 :(得分:2)

这不是'非法',只是简单的实施定义。实际上,在某些平台(例如DOS)上,需要特定的内存地址,例如将文本写入以0xB8000开头的视频缓冲区,或者将内存映射到SNES上的控制器I / O.

在大多数当前的操作系统中,出于安全原因使用了一个名为ASLR的功能,它使古老的专用地址模式成为过去,有利于通过驱动程序和内核层,这就是对于你运行它的大多数地方来说都是“非法的”。

答案 2 :(得分:1)

这里最基本的问题是你没有将ptr分配给有效的内存地址,在某些情况下0是有效的内存地址但通常不是。{1}}。由于ptr在您的第一种情况下是全局变量,因此它将初始化为0。雷米巴尔问了一个很好的跟进问题,best answer让我意识到这是一个重新宣布:

*ptr = 2;

然后您将ptr设置为2值,除非偶然指向有效的内存地址。

如果ptr是本地或自动变量,那么它将是未初始化的,并且它的值将是不确定的。在C和C ++中,使用具有不确定值的指针为undefined behavior。在大多数情况下,使用NULL指针也是未定义的行为,尽管允许实现来定义行为。

在大多数尝试访问内存的现代系统中,您的进程不拥有将导致segmentation fault

您可以通过几种方式为ptr分配有效的内存地址,例如:

int k = 7;
int *ptr = &k;
           ^

请注意使用&获取k的地址,或者您可以使用malloc为其动态分配内存。

答案 3 :(得分:0)

您的代码无效,但有些C编译器可能允许它与旧版本的语言兼容。

语句(包括赋值语句)如果出现在函数体外,则是非法的(语法错误)。

你有:

int *ptr;
*ptr = 2;

在文件范围内。第一行是int*对象的有效声明,名为ptr,隐式初始化为空指针值。第二行看起来像,就像赋值语句一样,但由于它在函数之外,编译器很可能甚至不会尝试以这种方式解释它。 gcc将其视为声明。旧版本的C允许您在声明中省略类型名称; C99删除了“隐式int”规则。所以gcc对待

*ptr = 2;

等同于

int *ptr = 2;

并产生以下警告:

c.c:4:1: warning: data definition has no type or storage class [enabled by default]
c.c:4:8: warning: initialization makes pointer from integer without a cast [enabled by default]

第一个警告是因为您从声明中省略了int(或其他类型名称)。第二个是因为2int类型的值,并且您正在使用它来初始化int*类型的对象;没有从intint*的隐式转换(除了空指针常量的特殊情况)。

一旦你超越了它,你有两个相同对象的声明 - 但它们是兼容的,所以这是允许的。并且指针变量被初始化为(int*)2,这是一个垃圾指针值(在内存地址0x00000002上不太可能有用)。

main功能中,您可以:

printf("%d\n", *ptr);

尝试在该内存地址处打印int对象的值。由于该地址不太可能是您的程序有权访问的地址,因此分段错误并不是一个令人惊讶的结果。 (更一般地说,行为未定义。)

(这是C中一个相当常见的问题:程序中的小错误可能会导致某些内容仍然编译,但与您的意图完全不同。我想到的方式是C的语法相对“密集” ;对有效程序的小型随机调整通常会产生不同但语法上有效的程序,而不是创建语法错误。)

这就是你的程序实际上做的事情;我确定这不是你打算做的。

深吸一口气,继续阅读。


这可能更接近你的意图:

#include <stdio.h>

int *ptr;

int main(void) {
    *ptr = 2;
    printf("%d\n", *ptr);
    return 0;
}

由于现在没有ptr的初始化器,因此它被隐式初始化为空指针值。 (如果在ptr中定义main,则其初始值将为垃圾。)赋值语句尝试取消引用该空指针,从而导致分段错误(同样,行为未定义;分段错误可能是一个结果)。执行永远不会到达printf电话。

  

我认为*ptr=2会将指针ptr指向的rvalue设置为2.是不是这样?

不完全。指针不指向右值; “rvalue”仅仅是表达式的价值。指针指向对象(如果它们指向任何东西)。作业

*ptr = 2;

将值2分配给ptr指向的对象 - 但ptr不指向对象!

现在让我们看一下你的程序实际可行的版本:

#include <stdio.h>

int *ptr;
int variable;

int main(void) {
    ptr = &variable;
    *ptr = 2;
    printf("*ptr     = %d\n", *ptr);
    printf("variable = %d\n", variable);
    return 0;
}

现在ptr指向一个对象,*ptr = 2为该对象指定一个值。输出是:

*ptr     = 2
variable = 2