一、指针没初始化就使用

新手最容易犯的错误就是忘记给指针分配内存就直接使用。想象一下,你拿着一个空杯子去接水——结果只能是水洒一地。Pascal中的指针也是这样,必须先分配内存才能操作。

// 示例1:未初始化指针导致的运行时错误
program UninitializedPointerDemo;
type
  PInteger = ^Integer;
var
  p: PInteger; // 只声明了指针,没有分配内存
begin
  p^ := 100;   // 直接操作空指针,程序会崩溃
end.

正确的做法是使用New分配内存:

// 修正后的正确示例
program SafePointerDemo;
var
  p: ^Integer;
begin
  New(p);      // 关键步骤:分配内存
  p^ := 100;   // 现在可以安全操作
  Dispose(p);  // 用完记得释放
end.

二、忘记释放内存

分配了内存却忘记释放,就像吃完饭不洗碗,迟早会把厨房堆满。Pascal需要手动调用Dispose释放内存,否则会导致内存泄漏。

// 示例2:内存泄漏场景
program MemoryLeakDemo;
var
  arr: array[1..100] of ^Integer;
  i: Integer;
begin
  for i := 1 to 100 do
  begin
    New(arr[i]);  // 分配了100次内存
    arr[i]^ := i;
    // 但没有调用Dispose!
  end;
end.

改进方案:

// 添加内存释放逻辑
program FixedMemoryDemo;
var
  arr: array[1..100] of ^Integer;
  i: Integer;
begin
  for i := 1 to 100 do
  begin
    New(arr[i]);
    arr[i]^ := i;
  end;
  
  // 在程序结束前释放所有内存
  for i := 1 to 100 do
    Dispose(arr[i]);
end.

三、指针越界访问

指针就像带箭头的导航,如果指向错误的位置就会出事故。特别是在操作数组时:

// 示例3:指针越界导致的数据混乱
program PointerOutOfBound;
var
  p: ^Integer;
  arr: array[1..3] of Integer = (10, 20, 30);
begin
  p := @arr[1];  // 指向第一个元素
  WriteLn(p^);    // 输出10(正确)
  
  Inc(p);         // 移动到下一个内存位置
  WriteLn(p^);    // 输出20(正确)
  
  Inc(p, 5);      // 危险!跳出了数组范围
  WriteLn(p^);    // 可能输出垃圾数据或崩溃
end.

安全操作建议:

// 使用范围检查的改进版
program SafeArrayAccess;
var
  p: ^Integer;
  arr: array[1..3] of Integer = (10, 20, 30);
begin
  p := @arr[Low(arr)];  // 始终从合法起点开始
  for i := 0 to High(arr)-1 do
  begin
    WriteLn((p + i)^);  // 确保在数组范围内移动
  end;
end.

四、悬垂指针问题

释放内存后继续使用指针,就像用已经注销的银行卡取钱——必然失败。这类错误在复杂程序中尤其隐蔽:

// 示例4:悬垂指针引发的错误
program DanglingPointerDemo;
var
  p: ^String;
begin
  New(p);
  p^ := 'Hello';
  Dispose(p);  // 内存已释放
  
  // 危险操作开始!
  p^ := 'World';  // 修改已释放的内存
  WriteLn(p^);    // 可能输出乱码或崩溃
end.

防御性编程方案:

// 使用nil指针的规范写法
program NilPointerPractice;
var
  p: ^String = nil; // 初始化为空
begin
  New(p);
  p^ := 'Safe';
  
  // 释放后立即置空
  Dispose(p);
  p := nil;
  
  if p <> nil then  // 安全检测
    WriteLn(p^)
  else
    WriteLn('指针已释放'); // 友好提示
end.

五、结构体指针的特殊性

操作包含指针的结构体时,就像拆装乐高玩具,要小心每个零件的归属:

// 示例5:嵌套指针的内存管理
program RecordPointerDemo;
type
  PStudent = ^TStudent;
  TStudent = record
    name: ^String;
    age: Integer;
  end;

var
  s: PStudent;
begin
  New(s);
  New(s^.name);  // 必须单独为name分配内存
  
  s^.name^ := 'Alice';
  s^.age := 18;
  
  // 释放时需要逆向操作
  Dispose(s^.name);
  Dispose(s);
end.

应用场景与技术建议

指针在Pascal中的典型使用场景包括:

  1. 动态数据结构(如链表、树)
  2. 大型内存块的高效传递
  3. 底层系统编程

优点

  • 直接内存操作效率极高
  • 灵活实现复杂数据结构

缺点

  • 容易引发内存问题
  • 调试困难

注意事项

  1. 坚持"谁分配谁释放"原则
  2. 复杂项目建议使用内存检测工具
  3. 考虑改用更安全的现代替代方案(如动态数组)

总结

Pascal指针就像一把双刃剑,用得好可以提升程序性能,用不好则会导致各种难以排查的问题。关键要养成三个习惯:初始化时分配内存、使用前检查有效性、完成后立即释放。对于现代开发,除非有特殊需求,否则建议优先考虑更安全的内存管理方式。