c-我只是想知道为什么下面的代码在执行时没有给出分段错误



我有一个打印双链表元素的函数。我将列表的头传递给函数,并在函数中使用另一个变量。在不将内部变量分配给传递的变量的情况下,我可以访问列表元素。[注意-AddtoHead功能未显示],现在添加以提高的清晰度

#include <stdio.h>
#include <stdlib.h>    
struct Node {
int data;
struct Node *next;
struct Node *prev;
};
void AddtoHead(struct Node **head_ptr, int data) {
struct Node *new_node = (struct Node*)malloc(sizeof(struct Node));
new_node->data = data;
new_node->next = (*head_ptr);
new_node->prev = NULL;
if ((*head_ptr) != NULL)
(*head_ptr)->prev = new_node;
(*head_ptr) = new_node;
return;
}
void PrintFList(struct Node *node) {
struct Node* last;
printf("nWalking FAKE List : n");
printf("%d-> nn", last->data);
last = last->next;
printf("%d-> nn", last->data);    
return;
}
/* Let's another function to see if it exhibits the same behaviour. A fun Mull to multiply two nos which would be passed as argument */
int Mull(int a, int b) {
int c, d;
printf("nn%d  %dn", c, d);
return c*d;
}
int main()
{
// Start with the empty list 
struct Node* head = NULL;
int i;
// Insert through a for loop
for (i = 0; i < 5; i++)
AddtoHead(&head, i+10);
PrintFList(head);
// Mull() function does not show the same behaviour 
printf("nnMultiplication of a and b: %d nn", Mull(i, i+1));
return 0;
}

它打印以下内容:

Walking FAKE List :
14->
13->

而我预计会出现分段故障,因为最后一个没有分配

访问未初始化的指针会导致未定义的行为。这意味着一切都可能发生,这取决于运行时可能发生的许多场景。

PrintFList()函数的last局部变量的值存储在进程堆栈中。由于它从未初始化过,因此它包含调用之前写入其中的任何值。

碰巧来自main的前一个调用是AddtoHead(14),并且由于它有一个与PrintFList兼容的签名(这意味着两者都返回void,并且都有一个单指针参数,所以它们的调用将以相同的方式使用堆栈),所以它的第一个局部变量(node)占用的位置与last稍后占用的位置完全相同。

node的值是多少?好吧,这是最后插入的节点的有效地址,这就是为什么没有出现分段错误,并且打印完全从最后插入的结点("-> 14")开始。

如何获得分段错误
当然,这种明显正确的行为只是运气:在PrintFList()之前的任何进一步调用都可能覆盖堆栈的该位置,并且会发生分段错误。

为了获得自第一次尝试以来所期望的segfault,您只需在AddtoHead(14)PrintFList()之间调用任何包含一些局部变量的函数。一个简单的例子是…printf()(它肯定有一个va_list局部变量来解析其变元参数):

int main()
{
// Start with the empty list 
struct Node* head = NULL;
int i;
// Insert through a for loop
for (i = 0; i < 5; i++)
AddtoHead(&head, i+10);

printf("hello %d times!n", i); 
PrintFList(head); // --> Segmentation fault!
}

当程序试图访问未映射到虚拟地址空间中的内存或已映射但未获得所尝试访问类型的权限的内存时,会导致分段错误。

当一个对象没有初始化时,编译器可以为它使用任何值。你的程序可能会从某个处理器寄存器中获取一个值(如果你初始化了它,处理器会使用这个寄存器来处理last)。该寄存器可能有虚拟内存中某个地方的地址,您的程序确实有数据,因此它被映射到您的虚拟地址空间中,您可以访问它。这并非不可能,因为您的程序一直在使用处理器寄存器进行各种事情,如计算或访问内存,所以有时它们中有有效的内存地址。

当这种情况发生时,您的程序会访问内存,并且不会出现分段错误。

寄存器中除了有效地址之外也不太可能有其他内容,例如计算某个表达式或从表达式的中间部分计算某些值的结果。

根据Roberto Caboni的评论,我对代码进行了一些测试,以证明他的观点。我添加了一个小函数Mull(),它取两个整数并返回a*b。然后我在PrintFList()函数之前调用这个函数,这会导致分段错误。

int Mull(int a, int b) {
printf("nna is: %d                b is: %dn", a, b);
return a*b;
}

然后,按以下顺序从main()调用它,(如果首先调用PrintFList,问题仍然存在)

printf("Multiplication of a and b - (1st Mull call): %d ", Mull(i, i+1));
PrintFList(head);

它给出以下输出:

a is: 5            b is : 6
Multiplication of a and b - (1st Mull call): 30
Walking FAKE List :
Segmentation fault (core dumped)

现在,这带来了另一个问题——不是所有的局部变量都在编译时分配吗??函数变量似乎是在被调用时被分配的。否则,我无法解释为什么这与Mull()函数一起工作。

我已经标记了上面问题的答案。为了深入理解这一点,让我举一个简单的例子:让我们写两个函数,将两个作为参数传递给它们的整数相乘。第一个Mul1()fun将只有2个int var,第二个Mul2()fun将有4个int var-2作为Mul2(

#include <stdio.h>
// Multiply fun with two integer variables
int Mul1(int a, int b) {
printf("nna is: %d -- b is: %d n", a, b);
return a*b;
}
// Multiply fun with four integer variables
int Mul2(int a, int b) {
int c, d;
printf("nnc is: %d -- d is: %dn", c, d);
return c*d;
}
int main()
{
int i = 5;
printf("Multiplication of a and b - (1st Mull call): %d ", Mul1(i, i+1));
printf("Multiplication of a and b - (2nd Mull call): %d ", Mul2(i, i+1));
printf("nn");
return 0;
}

它给出了以下输出-从输出中可以看出,变量c,d占据了之前调用Mul1()fun的变量b,a的位置。因此,尽管c,d没有被赋予任何值,但它仍然打印6 x 5=30,这很奇怪!

a is: 5 -- b is: 6
Multiplication of a and b - (1st Mul1 call): 30
c is: 6 -- d is: 5
Multiplication of a and b - (2nd Mul2 call): 30

因此,我有兴趣在每个Mul()函数中打印a、b、c、d的地址,看看它们占据了哪个空间。在我修改/添加printf()语句到打印地址的那一刻,问题变得不可再现。这也是Roberto Caboni解释的,因为printf()中的va_list参数-修改,Mul1()和Mul2()printf()如下,问题将消失,(c*d)将打印一些垃圾值,这是预期的行为。

printf("nna is: %d -- b is: %d  -- Address of a is: %p && Address of b is: %pn", a, b, &a, &b);
printf("nnc is: %d -- d is: %d  -- Address of c is: %p && Address of d is: %pn", c, d, &c, &d);

在修改了Mul1()和Mul2()函数中的printf()语句后,out如下所示:从地址打印中,我们可以看到,这一次,所有变量a、b、c、d都获得了从0x84a0到0x84ac的唯一地址,在堆栈中占据了16个字节(每个整数各4个字节)

a is: 5 -- b is: 6  -- Address of a is: 0x7ffd187084ac && Address of b is: 0x7ffd187084a8
Multiplication of a and b - (1st Mul1 call): 30
c is: -1403531320 -- d is: 32522  -- Address of c is: 0x7ffd187084a0 && Address of d is: 0x7ffd187084a4
Multiplication of a and b - (2nd Mul2 call): 1266832848

最新更新