为什么这段 C 代码会在 macOS 上生成分段错误,而在其他系统上不会?
Why does this C code generate a segmentation fault on macOS, but not on other systems?
在尝试用 C 实现双向链表时,我注意到以下代码片段会在 macOS 10.11 El Capitan 上引发分段错误。但是,在 Linux 或 Haiku 中测试时,它会 运行 愉快地生成预期结果。
#include <stdio.h>
#include <stdlib.h>
typedef struct node_structure {
int data;
struct node_structure *prev;
struct node_structure *next;
} *node;
node createNode(int value) {
node newNode = (node) malloc(sizeof(node));
if (newNode != NULL) {
newNode->data = value;
newNode->prev = NULL;
newNode->next = NULL;
}
return newNode;
}
void displayLinkedList(node linked_list) {
node cursor = linked_list;
while (cursor != NULL) {
printf("DATA: %d \tTHIS:%p \tPREV:%p \tNEXT:%p\n", cursor->data, (void*)cursor, (void *)cursor->prev, (void *)cursor->next);
cursor=cursor->next;
}
}
int insertAtHead(node *head, int value) {
node newHead = createNode(value);
if(newHead != NULL) {
(*head)->prev = newHead;
newHead->next = *head;
*head = newHead;
return 0;
}
else return 1;
}
int main() {
printf("\nCreating a single element linked list.\n");
node head = createNode(10);
displayLinkedList(head);
printf("\nInserting 10 elements at head.\n");
for(int i = 0; i < 10; i++) {
insertAtHead(&head, 8);
}
displayLinkedList(head);
return 0;
}
这是控制台输出:
$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 8.0.0 (clang-800.0.42.1)
Target: x86_64-apple-darwin15.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
$ gcc -Wall -pedantic 04_doubly_linked_lists__debugging.c
$ ./a.out
Creating a single element linked list.
DATA: 10 THIS:0x7fd19a403390 PREV:0x0 NEXT:0x0
Inserting 10 elements at head.
DATA: 8 THIS:0x7fd19a403430 PREV:0x0 NEXT:0x7fd19a403420
DATA: 8 THIS:0x7fd19a403420 PREV:0x7fd19a403430 NEXT:0x7fd100000008
Segmentation fault: 11
如您所见,在崩溃前的最后一次迭代中,next
指针似乎被结构的 data
字段中的值覆盖(在此示例中,值为 8 的整数)。
特别奇怪的是,相同的代码 运行 在其他操作系统中没有任何问题,完成了 10 个元素的插入循环,并将所有元素和各自的内存地址正确显示到屏幕上。
我是不是做错了什么?
您已将 node
声明为指针类型,因此 malloc(sizeof node)
为指针分配了足够的内存,但没有为结构分配足够的内存。如果它成功了,那纯属偶然。
分配指针时要养成的一个好习惯是始终使用以下形式:
fooptr = malloc(sizeof *fooptr);
这样一目了然,您正在分配指针指向的对象的大小,甚至可以在不更改代码的情况下更改类型。
问题是这样的:
node newNode = (node) malloc(sizeof(node));
如果您不想修改任何其他内容,您可以更正它:
node newNode = (node) malloc(sizeof(*node));
但是,我想在您的代码中解决一些问题。首先,不要强制转换 malloc
,因为它完全没有必要,除非您出于某种原因正在使用 C++ 编译器。
其次,将变量而不是类型作为 malloc
的参数要好得多,因为它避免了代码重复。在这种情况下,它也可以解决您的错误。有了这两件事,你可以这样写:
node newNode = malloc(sizeof(*newNode));
第三,完全没有理由为 node_structure
和 node
使用不同的名称。改为这样写:
typedef struct node {
int data;
struct node *prev;
struct node *next;
} *node;
第四,当你创建一个库的接口时,你可以使用 typedefs 来隐藏结构和指针的下降(有些人争论以这种方式隐藏指针)有效性,但不要在实际操作它们的代码中使用它们.您的创建应如下所示:
struct node *createNode(int value) {
struct node *newNode = malloc(sizeof(*newNode));
// Same as before in the rest
What makes this especially weird is that the same code runs without any trouble in other operating systems
这并不奇怪。几乎可以 100% 确定您的代码具有 未定义的行为。
在尝试用 C 实现双向链表时,我注意到以下代码片段会在 macOS 10.11 El Capitan 上引发分段错误。但是,在 Linux 或 Haiku 中测试时,它会 运行 愉快地生成预期结果。
#include <stdio.h>
#include <stdlib.h>
typedef struct node_structure {
int data;
struct node_structure *prev;
struct node_structure *next;
} *node;
node createNode(int value) {
node newNode = (node) malloc(sizeof(node));
if (newNode != NULL) {
newNode->data = value;
newNode->prev = NULL;
newNode->next = NULL;
}
return newNode;
}
void displayLinkedList(node linked_list) {
node cursor = linked_list;
while (cursor != NULL) {
printf("DATA: %d \tTHIS:%p \tPREV:%p \tNEXT:%p\n", cursor->data, (void*)cursor, (void *)cursor->prev, (void *)cursor->next);
cursor=cursor->next;
}
}
int insertAtHead(node *head, int value) {
node newHead = createNode(value);
if(newHead != NULL) {
(*head)->prev = newHead;
newHead->next = *head;
*head = newHead;
return 0;
}
else return 1;
}
int main() {
printf("\nCreating a single element linked list.\n");
node head = createNode(10);
displayLinkedList(head);
printf("\nInserting 10 elements at head.\n");
for(int i = 0; i < 10; i++) {
insertAtHead(&head, 8);
}
displayLinkedList(head);
return 0;
}
这是控制台输出:
$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 8.0.0 (clang-800.0.42.1)
Target: x86_64-apple-darwin15.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
$ gcc -Wall -pedantic 04_doubly_linked_lists__debugging.c
$ ./a.out
Creating a single element linked list.
DATA: 10 THIS:0x7fd19a403390 PREV:0x0 NEXT:0x0
Inserting 10 elements at head.
DATA: 8 THIS:0x7fd19a403430 PREV:0x0 NEXT:0x7fd19a403420
DATA: 8 THIS:0x7fd19a403420 PREV:0x7fd19a403430 NEXT:0x7fd100000008
Segmentation fault: 11
如您所见,在崩溃前的最后一次迭代中,next
指针似乎被结构的 data
字段中的值覆盖(在此示例中,值为 8 的整数)。
特别奇怪的是,相同的代码 运行 在其他操作系统中没有任何问题,完成了 10 个元素的插入循环,并将所有元素和各自的内存地址正确显示到屏幕上。
我是不是做错了什么?
您已将 node
声明为指针类型,因此 malloc(sizeof node)
为指针分配了足够的内存,但没有为结构分配足够的内存。如果它成功了,那纯属偶然。
分配指针时要养成的一个好习惯是始终使用以下形式:
fooptr = malloc(sizeof *fooptr);
这样一目了然,您正在分配指针指向的对象的大小,甚至可以在不更改代码的情况下更改类型。
问题是这样的:
node newNode = (node) malloc(sizeof(node));
如果您不想修改任何其他内容,您可以更正它:
node newNode = (node) malloc(sizeof(*node));
但是,我想在您的代码中解决一些问题。首先,不要强制转换 malloc
,因为它完全没有必要,除非您出于某种原因正在使用 C++ 编译器。
其次,将变量而不是类型作为 malloc
的参数要好得多,因为它避免了代码重复。在这种情况下,它也可以解决您的错误。有了这两件事,你可以这样写:
node newNode = malloc(sizeof(*newNode));
第三,完全没有理由为 node_structure
和 node
使用不同的名称。改为这样写:
typedef struct node {
int data;
struct node *prev;
struct node *next;
} *node;
第四,当你创建一个库的接口时,你可以使用 typedefs 来隐藏结构和指针的下降(有些人争论以这种方式隐藏指针)有效性,但不要在实际操作它们的代码中使用它们.您的创建应如下所示:
struct node *createNode(int value) {
struct node *newNode = malloc(sizeof(*newNode));
// Same as before in the rest
What makes this especially weird is that the same code runs without any trouble in other operating systems
这并不奇怪。几乎可以 100% 确定您的代码具有 未定义的行为。