在 C 中,为什么不声明一个指针并使其直接指向一个数字,而无需 malloc?

In C, why can't you declare a pointer and make it point to a number directly, without malloc?

在 C 中,为什么 X 和 Y 有效而 Z 无效?

//X     
int num = 5;
int *ptr;
ptr = #

//Y
int *mptr = (int *)malloc(sizeof(int *));       
*mptr = 5;       

//Z
int* myptr;
*myptr = 5;

在 Z 中,我声明了一个指针并试图让它指向一个数字,但我遇到了分段错误。但在我看来,我在 Z 中做的事情与 X 和 Y 一样。在 X 中我只是使用变量 num 而不是直接使用数字 5,而在 Y 中我只是使用堆来保存变量而不是堆栈。那么,为什么 X 和 Y 起作用而 Z 不起作用呢?

您必须先为变量赋值,然后才能使用该值。在 Z 中,您在给它赋值之前使用 myptr 的值。你的其他两个例子都没有这样做。

//X     
int num = 5; // Assign num a value
int *ptr;
ptr = # // Assign ptr a value

//Y
int *mptr = (int *)malloc(sizeof(int *));     // Assign mptr a value
*mptr = 5;       // Use the value

//Z
int* myptr; // Doesn't assign myptr a value
*myptr = 5; // Uses myptr's value

您没有指定 myptr 指向什么。您仅将其声明为指针(即用于指向某处的变量),但您没有对其进行初始化。在 X 和 Y 中你确实初始化了指针:

//X     
int num = 5;
int *ptr;       /* Declaration */
ptr = #     /* Initialization */

//Y
int *mptr = (int *)malloc(sizeof(int));   /* Declaration + Initialization*/ 
*mptr = 5;      /* De-referencing*/ 

//Z
int* myptr;  /* Declaration only */
*myptr = 5;  /* De-referencing before initialization */

在 Z 中只有声明:int* myptr。您正在尝试取消引用下一行中的指针,即 *myptr = 5;。这会导致段错误,因为 myptr 尚未初始化。

编辑

请注意,正如 David Hoelzer 所指出的,当您调用 malloc 时,您可能希望编写 malloc(sizeof(int)) 而不是 malloc(sizeof(int*))

int num = 5; // name a memory location "num" and store 5 in it
int *ptr;    // name a memory location "ptr"
ptr = #  // store address of "num" in "ptr"

//Y
int *mptr = (int *)malloc(sizeof(int *)); // name a memory location mptr
// then (wrongly, should have been sizeof(int)) allocate enough memory to store
// a pointer and store the address of that allocated memory in "mptr"
*mptr = 5;  // write value 5 in that anonymous memory location allocated by malloc
// that mptr is pointing at

//Z
int* myptr; // name a memory location "myptr", don't write anything to it
*myptr = 5; // try to write value of 5 into memory pointed by myptr -- 
//except myptr is not pointed anywhere yet, get undefined behavior

阻碍你的不一定是 C 语言,而是你的操作系统。

  • 在情况 X 中,您的内存需求可以从您的代码中推断出来,并且在您的程序执行之前分配内存。
  • 在情况 Y 中,您请求动态分配内存并且操作系统尝试满足该请求。
  • 在案例Z中,无法从代码中推导出内存需求,也没有向操作系统请求动态分配内存;因此,没有分配内存:您在尝试引用未分配的内存时遇到分段错误。

段错误是由操作系统发出的,以防止您取消引用未分配的内存;它是现代操作系统中的一项安全功能,旨在防止您的程序破坏系统中其他地方使用的数据。

在 Z 情况下,您的指针可能设置为随机地址。如果您引用此地址并在该位置存储 5,您可能会覆盖内存中另一个程序的数据。

想象一下由于程序相互干扰内存中的数据而引起的混乱:您不能再相信您的变量,因为另一个程序可能已经弄乱了它们,或者更糟的是 - 您程序中的一个小错误可能会覆盖你的操作系统在内存中并使你的整个机器崩溃;这就是现代操作系统阻止您这样做的原因。

如果你在裸机上编程(即没有操作系统),你可以尊重你想要的所有随机内存地址,因为没有什么可以阻止你搬起石头砸自己的脚。

//X     
int num = 5;
int *ptr;
ptr = #

对于上面的内容,int值"num"分配在堆栈上,或者在程序数据段中,因此它有一个地址。让我们假设它是由编译器分配的地址 0x12345678。您创建一个 int* ptr。这也有地址可以说是 0x20000000。该地址当前指向随机数据。我们想让 0x20000000 处的指针指向 0x12345678 处的数据值,这样我们就可以读取 0x12345678 的内容并取回值 5...所以我们将 0x12345678 放入存储 space 处的 0x20000000(我们设置 ptr = &num).

//Z
int* myptr;
*myptr = 5;

对于第二个例子,我们只有 0x20000000 (myptr)。它是一个指针,目前指向任何地方或任何地方。当我们执行 *myptr = 5 时,我们查看存储在 0x20000000 的地址。它是随机的,所以它可能是 0xffff0000 让我们使用那个例子。然后它将尝试将值 5 写入该地址 (0xffff0000),该地址不存在并导致段错误。

所以在你的最后一个例子中,指针存在,但它没有指向任何有效的地方,所以当你试图写入它指向的地方时,你要么破坏有效内存,要么导致段错误。

事实上,从语法上讲,您可以使用显式转换来完成:

//Z
int* myptr;
myptr = (void *)5;

所以它可以(危险地)用作 int 但如果您尝试取消引用它,您将面临分段错误,因为 myptr 指向地址 0x5那是无效的.

指针必须指向内存位置。 (当然除非指针为空或无效,但在那种情况下你无法通过它写入)。

表达式5 不表示内存位置。那里没有一些内存 5 等待被指向。由于这个原因,我们说 5 是一个 value(或有时 r-value)。

do 指示内存位置的表达式称为 l-values 。 (您可以认为 l 代表 location)。如果要指向包含 5 的位置,则必须包含一些保留内存区域的代码。此代码将使用左值来引用该内存位置。需要明确的是,术语 l-value 表示表达式,而不是内存位置。

在您的代码中:

  • X:int num = 5;保留一个位置,命名为num,并在其中存储5。左值为 num.
  • Y: malloc(sizeof(int) (sic)保留一个位置,*mptr = 5;在里面存一个值。左值为 *mptr .
  • Z: *myptr 是一个左值,但是 它不指定内存位置 因为你没有让 myptr 指向任何内存.所以这段代码可以编译,但它会导致未定义的行为:左值必须在它被评估的时候指定一个有效的内存位置。

注意。教程通常对 l 值和 r 值的解释很差,因此在谷歌搜索时要小心。 (我找不到任何仅 C 语言的好页面)。

  1. 指针被称为“指针”是有原因的。数据指针只能指向 lvalues,即指向在存储(内存)中有位置的对象。

    “一个数字”在内存中没有位置。 “数字”不是左值。无法指向没有位置的东西。

  2. 您标记为 Z 的示例甚至看起来都不像试图使指针指向数字。您标记为 X 和 Y 的示例清楚地表明,为了使指针 ptr 指向某处,您必须直接将值分配给 ptr,而不是 *ptr。在您的 Z 示例中,您永远不会为 myptr.

    分配任何值

你不能这样做,因为在此声明中,Z 只是一个指针,不包含任何内存。 你需要 "point" 用 Z 到一个新的内存

您可以通过 3 种方式做到这一点:

int* myptr;
myptr(int *)malloc(sizeof(int));

myptr = 5;

int* myptr = new int;

int A;
int* myptr = A;

您的失败代码

//Z
int* myptr;
*myptr = 5;

执行以下操作(假设 32 位地址和 32 位 int):

  1. int* myptr;myptr 声明为指向 int 的指针。也就是说,它 持有 int 地址 。由于您尚未 初始化 myptr,因此 myptr 包含未识别的值。它可能是绝对地址 0x00000000 或一些看似随机的值(最有可能是最后一次占用内存中该位置的位)。

  2. *myptr = 5; 是一条指令,用于*将整数值 5 (0x00000005) 保存在位于 myptr 中包含的地址的 4 个连续内存字节中.您是否可以写入 到内存中的那个位置完全取决于那个地址是什么。如果幸运的话,您的程序会出错并死掉。如果你不走运,它会破坏一些重要的东西(你的调用堆栈链接,也许,或者更糟的东西)来破坏内存。

然后,您的程序将 运行 正常...直到它以奇怪和神秘的方式崩溃。你会绞尽脑汁想弄清楚发生了什么。别问我怎么知道的

现在,你可以

myptr = 5 ;

它做了一些不同的事情:它分配 myptr 绝对地址 0x00000005。那么如果你说这样的话:

*myptr = 3*5 ;

它将尝试将整数值 15 (0x0000000F) 存储在位于绝对地址 0x00000005 的 4 个字节中。它仍然(几乎可以肯定)会崩溃,但至少会以一致且可重现的方式崩溃。

为了论证,我的示例地址假设为 32 位地址 space。

指针是保存其他内存地址的变量spaces.When声明了一个指针,它没有任何内存,必须通过将其分配给某个已经存在的变量或内存来显式初始化使用 malloc() C 库函数。

在您的 X 案例中,ptr 指向 num 的地址,因此它反映了 num 被引用后的值。

与案例 Y 类似, 显式分配内存,并使用

使指针 mptr 指向它
int *mptr = (int *)malloc(sizeof(int *)); 

但是在Z的情况下,指针只是被声明了,但是并没有赋值或分配任何内存位置。所以指针指向一些超出执行程序范围的野外位置,因此最终给出分段错误,这通常意味着超出范围的内存访问。