野指针是指向随机内存地址的未初始化指针,是最危险的指针错误之一。
int *p; // 野指针:未初始化
*p = 10; // 危险!可能修改任意内存
预防措施:
悬空指针指向已经释放或失效的内存,解引用会导致未定义行为。
int *p = malloc(sizeof(int));
*p = 100;
free(p);
*p = 200; // 悬空指针:解引用已释放内存
p = NULL; // 正确:释放后置为NULL
预防措施:
数组越界是C语言中最常见的错误之一,可能导致数据损坏或程序崩溃。
int arr[5] = {1, 2, 3, 4, 5};
arr[10] = 100; // 越界:未定义行为
arr[-1] = 0; // 越界:未定义行为
预防措施:
解引用NULL指针会导致段错误(Segmentation Fault)。
int *p = NULL;
*p = 10; // 段错误
预防措施:
指针类型转换可能导致步长变化,引发意想不到的错误。
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
char *cp = (char*)p;
cp++; // 只移动1字节,不是4字节
printf("%d\n", *cp); // 可能输出错误值
预防措施:
函数返回局部变量的地址是未定义行为,因为局部变量在函数结束后被销毁。
int* wrong_func() {
int local = 10;
return &local; // 危险!
}
int *p = wrong_func();
printf("%d\n", *p); // 未定义行为
预防措施:
动态分配的内存未释放,导致内存泄漏。
void leak() {
int *p = malloc(sizeof(int));
*p = 10;
// 忘记free(p)
}
// 多次调用leak()会导致内存泄漏
预防措施:
指针和数组在大多数情况下可以互换,但有重要区别。
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
// sizeof不同
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 20
printf("sizeof(p) = %zu\n", sizeof(p)); // 4或8
// &操作符不同
int (*arr_ptr)[5] = &arr; // 指向整个数组
int *p_ptr = p; // 指向首元素
// 安全字符串操作
size_t strncpy(char *dest, const char *src, size_t n);
size_t strlcpy(char *dest, const char *src, size_t n);
// 安全输入
char *fgets(char *str, int n, FILE *stream);
// 内存检查
int memcmp(const void *s1, const void *s2, size_t n);
#include <assert.h>
assert(p != NULL); // 调试时检查
// Valgrind内存检查
// valgrind --leak-check=full ./program
// 检查指针有效性
int is_valid_ptr(void *p) {
return p != NULL;
}
// 安全释放
void safe_free(void **ptr) {
if (ptr != NULL && *ptr != NULL) {
free(*ptr);
*ptr = NULL;
}
}
#include <stdio.h>
#include <stdlib.h>
void demonstrate_wild_pointer() {
printf("=== 野指针演示 ===\n");
// 野指针:未初始化
int *wild_ptr;
// *wild_ptr = 10; // 危险!可能崩溃
// 正确做法:初始化为NULL
int *safe_ptr = NULL;
if (safe_ptr != NULL) {
printf("safe_ptr有效\n");
} else {
printf("safe_ptr是空指针\n");
}
// 分配内存后初始化
safe_ptr = (int*)malloc(sizeof(int));
if (safe_ptr != NULL) {
*safe_ptr = 100;
printf("safe_ptr = %d\n", *safe_ptr);
free(safe_ptr);
safe_ptr = NULL; // 释放后置为NULL
}
}
void demonstrate_dangling_pointer() {
printf("\n=== 悬空指针演示 ===\n");
int *p = (int*)malloc(sizeof(int));
*p = 200;
printf("释放前: *p = %d\n", *p);
free(p);
// *p = 300; // 危险!悬空指针
// 正确做法:释放后置为NULL
p = NULL;
if (p == NULL) {
printf("p已置为NULL\n");
}
// 多次释放同一指针
// free(p); // 错误:重复释放
}
int main() {
demonstrate_wild_pointer();
demonstrate_dangling_pointer();
return 0;
}
编译命令:gcc -o wild_dangling wild_dangling.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void demonstrate_out_of_bounds() {
printf("=== 数组越界演示 ===\n");
int arr[5] = {1, 2, 3, 4, 5};
// 正确访问
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
// 越界访问(未定义行为)
// arr[10] = 100; // 可能崩溃或数据损坏
// arr[-1] = 0; // 可能崩溃或数据损坏
// 安全的数组操作
int index = 3;
if (index >= 0 && index < 5) {
printf("安全访问 arr[%d] = %d\n", index, arr[index]);
} else {
printf("索引越界\n");
}
}
void demonstrate_null_pointer() {
printf("\n=== 空指针解引用演示 ===\n");
int *p = NULL;
// 检查指针有效性
if (p != NULL) {
printf("*p = %d\n", *p);
} else {
printf("p是空指针,不能解引用\n");
}
// 动态分配后检查
p = (int*)malloc(sizeof(int));
if (p != NULL) {
*p = 300;
printf("分配后: *p = %d\n", *p);
free(p);
p = NULL;
}
}
int main() {
demonstrate_out_of_bounds();
demonstrate_null_pointer();
return 0;
}
编译命令:gcc -o out_of_bounds_null out_of_bounds_null.c
#include <stdio.h>
void demonstrate_type_confusion() {
printf("=== 指针类型混淆演示 ===\n");
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
// 不同类型的指针
char *cp = (char*)p;
double *dp = (double*)p;
// 指针算术步长不同
printf("int指针步长: %zu字节\n", sizeof(int));
printf("char指针步长: %zu字节\n", sizeof(char));
printf("double指针步长: %zu字节\n", sizeof(double));
// 演示步长差异
printf("\n地址变化:\n");
printf("p (int*) = %p\n", (void*)p);
printf("p + 1 (int*) = %p\n", (void*)(p + 1));
printf("cp (char*) = %p\n", (void*)cp);
printf("cp + 1 (char*) = %p\n", (void*)(cp + 1));
// 错误:通过char指针访问int数组
printf("\n错误访问:\n");
printf("*cp = %d (应该是1)\n", *cp);
printf("*(cp + 1) = %d (应该是0)\n", *(cp + 1));
// 正确:使用正确类型
printf("\n正确访问:\n");
printf("*p = %d\n", *p);
printf("*(p + 1) = %d\n", *(p + 1));
}
int main() {
demonstrate_type_confusion();
return 0;
}
编译命令:gcc -o type_confusion type_confusion.c
#include <stdio.h>
#include <stdlib.h>
// 危险:返回局部变量地址
int* dangerous_func() {
int local_var = 100;
return &local_var; // 未定义行为
}
// 正确:返回全局变量指针
int global_var = 200;
int* get_global() {
return &global_var;
}
// 正确:返回静态变量指针
int* get_static() {
static int static_var = 300;
return &static_var;
}
// 正确:返回动态分配内存
int* get_dynamic(int value) {
int *p = (int*)malloc(sizeof(int));
if (p != NULL) {
*p = value;
}
return p;
}
// 正确:返回结构体指针
struct Point {
int x;
int y;
};
struct Point* create_point(int x, int y) {
struct Point *p = (struct Point*)malloc(sizeof(struct Point));
if (p != NULL) {
p->x = x;
p->y = y;
}
return p;
}
int main() {
printf("=== 返回局部变量地址演示 ===\n");
// 错误用法(未定义行为)
// int *p1 = dangerous_func();
// printf("危险函数: %d\n", *p1);
// 正确用法
int *p2 = get_global();
printf("全局变量: %d\n", *p2);
int *p3 = get_static();
printf("静态变量: %d\n", *p3);
int *p4 = get_dynamic(400);
if (p4 != NULL) {
printf("动态内存: %d\n", *p4);
free(p4);
}
struct Point *point = create_point(10, 20);
if (point != NULL) {
printf("结构体: (%d, %d)\n", point->x, point->y);
free(point);
}
return 0;
}
编译命令:gcc -o return_local return_local.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 内存泄漏示例
void leak_example() {
int *p = (int*)malloc(sizeof(int));
*p = 10;
// 忘记free(p) - 内存泄漏
}
// 正确的内存管理
void correct_memory_management() {
int *p = (int*)malloc(sizeof(int));
if (p != NULL) {
*p = 20;
free(p); // 正确释放
p = NULL; // 置为NULL
}
}
// 多次分配和释放
void multiple_allocations() {
int *arr[10];
for (int i = 0; i < 10; i++) {
arr[i] = (int*)malloc(sizeof(int));
if (arr[i] != NULL) {
*arr[i] = i * 10;
}
}
// 使用内存
for (int i = 0; i < 10; i++) {
if (arr[i] != NULL) {
printf("arr[%d] = %d\n", i, *arr[i]);
}
}
// 释放所有内存
for (int i = 0; i < 10; i++) {
if (arr[i] != NULL) {
free(arr[i]);
arr[i] = NULL;
}
}
}
// 安全的字符串复制
void safe_string_copy() {
char *src = "Hello, World!";
char *dest = (char*)malloc(strlen(src) + 1);
if (dest != NULL) {
// 安全复制
strncpy(dest, src, strlen(src) + 1);
printf("复制的字符串: %s\n", dest);
free(dest);
dest = NULL;
}
}
int main() {
printf("=== 内存管理演示 ===\n");
// leak_example(); // 会导致内存泄漏
correct_memory_management();
multiple_allocations();
safe_string_copy();
printf("程序结束\n");
return 0;
}
编译命令:gcc -o memory_leak memory_leak.c
Q1: 什么是野指针?如何避免? A: 野指针是未初始化的指针,指向随机内存地址。避免方法:1) 声明时立即初始化为NULL或有效地址;2) 使用前检查指针有效性;3) 使用内存检测工具。
Q2: 什么是悬空指针?如何处理? A: 悬空指针指向已释放的内存。处理方法:1) 释放内存后立即将指针设为NULL;2) 使用智能指针或内存管理库;3) 避免返回局部变量地址。
Q3: 指针类型混淆会导致什么问题? A: 指针类型转换会改变步长,导致指针算术错误。例如,将int*转换为char*后,p+1只移动1字节而不是4字节,可能导致数据访问错误。
Q4: 如何避免内存泄漏? A: 1) 每次malloc都有对应的free;2) 释放后将指针设为NULL;3) 使用内存检测工具如Valgrind;4) 考虑使用自动内存管理。
Q5: 笔试中常见的指针陷阱有哪些? A: 1) 指针和数组sizeof差异;2) 函数参数中数组退化;3) 返回局部变量地址;4) 指针算术越界;5) 多级指针误用;6) const指针与非常量指针的转换。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 安全的内存分配
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
return ptr;
}
// 安全的字符串操作
char* safe_strdup(const char *s) {
if (s == NULL) return NULL;
size_t len = strlen(s) + 1;
char *dup = (char*)safe_malloc(len);
strcpy(dup, s);
return dup;
}
// 链表节点(演示指针陷阱避免)
struct Node {
int data;
struct Node *next;
};
// 安全的链表插入
void safe_insert(struct Node **head, int data) {
struct Node *new_node = (struct Node*)safe_malloc(sizeof(struct Node));
new_node->data = data;
new_node->next = *head;
*head = new_node;
}
// 安全的链表删除
void safe_delete(struct Node **head, int data) {
struct Node *current = *head;
struct Node *prev = NULL;
while (current != NULL) {
if (current->data == data) {
if (prev == NULL) {
*head = current->next;
} else {
prev->next = current->next;
}
free(current);
return;
}
prev = current;
current = current->next;
}
}
// 安全的字符串连接
char* safe_strcat(const char *s1, const char *s2) {
if (s1 == NULL || s2 == NULL) return NULL;
size_t len1 = strlen(s1);
size_t len2 = strlen(s2);
char *result = (char*)safe_malloc(len1 + len2 + 1);
strcpy(result, s1);
strcat(result, s2);
return result;
}
int main() {
printf("=== 安全指针操作演示 ===\n");
// 安全内存分配
int *arr = (int*)safe_malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {
arr[i] = i * 10;
}
// 安全字符串操作
char *str1 = safe_strdup("Hello");
char *str2 = safe_strdup("World");
char *combined = safe_strcat(str1, " ");
char *final = safe_strcat(combined, str2);
printf("组合字符串: %s\n", final);
// 清理内存
free(arr);
free(str1);
free(str2);
free(combined);
free(final);
// 链表操作
struct Node *head = NULL;
safe_insert(&head, 1);
safe_insert(&head, 2);
safe_insert(&head, 3);
printf("链表: ");
struct Node *current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
// 删除节点
safe_delete(&head, 2);
printf("删除2后: ");
current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
// 释放链表
current = head;
while (current != NULL) {
struct Node *temp = current;
current = current->next;
free(temp);
}
printf("程序安全结束\n");
return 0;
}
编译命令:gcc -o safe_pointer safe_pointer.c
注意:此示例展示了如何安全地处理指针操作,避免常见的指针陷阱。实际项目中应考虑使用更高级的内存管理技术。