Bước tới nội dung

Con trỏ rỗng

Bách khoa toàn thư mở Wikipedia

Trong ngành điện toán, khái niệm con trỏ rỗng chỉ một biến con trỏ có một giá trị định sẵn khiến cho nó không trỏ được tới bất kỳ một đối tượng chính tắc nào. Các ngôn ngữ lập trình thường sử dụng con trỏ rỗng trong trường hợp cuối một list không rõ độ dài hoặc một tác vụ nào đó thất bại; cách sử dụng con trỏ rỗng tương đương với các kiểu rỗng và ứng với giá trị Nothing trong kiểu lựa chọn.

Không nên nhầm lẫn con trỏ rỗng với một biến con trỏ chưa được khởi tạo giá trị cho nó: mục đích của việc sử dụng con trỏ rỗng là để để bảo đảm phép so sánh không bằng với bất kỳ con trỏ nào trỏ tới một đối tượng chính tắc. Tuy nhiên, tùy vào từng ngôn ngữ và phương pháp lập trình của nó, một biến con trỏ chưa được khởi tạo không chắc chắn sẽ thực hiện được yêu cầu trên. Khi thực hiện phép so sánh, nó có thể cho ra kết quả bằng với một giá trị nào đó, là một con trỏ chính tắc; hoặc có thể bằng với một con trỏ rỗng khác. Và như thế, biến con trỏ này có thể đóng cả hai vai trò ở những thời điểm khác nhau.

Về mặt ngữ nghĩa, con trỏ rỗng (null pointer) khác với giá trị rỗng (null value). Con trỏ rỗng trong hầu hết ngôn ngữ lập trình có nghĩa là "không có giá trị", trong khi giá trị rỗng trong một cơ sở dữ liệu quan hệ có nghĩa là "có giá trị không rõ". Điều này tạo ra một khác biệt trong thực tế: hầu hết ngôn ngữ lập trình xem hai con trỏ rỗng bằng nhau, nhưng các hệ cơ sở dữ liệu quan hệ lại không coi hai giá trị rỗng là bằng nhau(vì chúng biểu diễn các giá trị không rõ, và như thế, không thể khẳng định chúng có bằng nhau không).

Trong C, hai con trỏ rỗng của bất kỳ kiểu nào cũng được đảm bảo là bằng nhau.[1] Macro NULL được dùng để định nghĩa một hằng số con trỏ rỗng,[2] mà trong tiêu chuẩn C99 có thể linh hoạt xem như một giá trị kiểu nguyên 0 được chuyển đổi tường minh hoặc bất tường minh qua kiểu void*.[3]

Tham chiếu ngược một con trỏ rỗng có thể dẫn đến kết quả là hệ thống sẽ cố gắng đọc hoặc ghi in từ một vùng nhớ không được trỏ tới - gây ra lỗi phân đoạn hoặc xâm phạm truy cập (access violation). Khi đó, chương trình sẽ gặp lỗi hỏng hóc hoặc một ngoại lệ (exception) sẽ được "ném" ra. Tuy vậy, trong một số trường hợp, đây không phải là vấn đề nặng nề. Thí dụ, với các máy x86, địa chỉ 0000:0000 có thể được đọc và thường là có thể ghi được, cho nên, phép tham chiếu ngược biến con trỏ rỗng là hoàn toàn hợp lệ, một số hệ quả không mong muốn tuy không dẫn đến sập hệ thống nhưng cũng gây ra lỗi thiếu định nghĩa (undefined). Nên chú ý là cũng có những trường hợp khiến cho tham chiếu ngược con trỏ rỗng là có chủ đích và được định nghĩa tốt; ví dụ như mã BIOS viết bằng ngôn ngữ C cho các thiết bị x86 16-bit có thể viết IDT ở địa chỉ vật lý 0 của máy bằng cách sử dụng phép toán tham chiếu ngược một con trỏ rỗng. Trình biên dịch có thể tối ưu hóa bằng cách bằng cách không biên dịch phép tham chiếu ngược đối với con trỏ rỗng, nhằm ngặn chặn lỗi phân đoạn tuy nhiên vẫn có thể gây ra những hành vi nguy cơ khác

Trong ngôn ngữ lập trình C++, macro NULL được kế thừa từ C, giá trị kiểu số nguyên zero được ưu tiên dùng để thể hiện một hằng số con trỏ.[4] Tuy vậy, C++11 giới thiệu một giá trị hằng số con trỏ nullptr để sử dụng thay thế.

Ngôn ngữ khác

[sửa | sửa mã nguồn]

Trong một số môi trường lập trình khác (at least one proprietary Lisp implementation, for example),[cần dẫn nguồn] giá trị được dùng làm con trỏ rỗng (được gọi là nil trong Lisp) có thể thực sự là một con trỏ trỏ đến một khối dữ liệu nội hàm được sử dụng trong một hiện thực hóa nào đấy (nhưng không dễ dàng mà một chương trình người dùng có thể dễ dàng truy cập), thế là, bằng một register, người ta có thể sử dụng nó làm một hằng số hữu ích và lấy nó làm cách truy cập nội hàm hiện thực hóa. Ta gọi đây là vector nil.

Với các ngôn ngữ sử dụng kiến trúc thẻ, một tagged union mà việc sử dụng nó bắt buộc phải quy định tường minh cách thức xử lý các trường hợp ngoại lệ có thể dùng để thay thế con trỏ rỗng; trong thực tế, con trỏ rỗng có thể được xem là một con trỏ được gắn thẻ và thẻ này có thể tính toán được.

Tham chiếu ngược

[sửa | sửa mã nguồn]

Vì con trỏ rỗng không tham chiếu tới bất kỳ một đối tượng có nghĩa nào, cố gắng thực hiện tham chiếu ngược (truy cập tới dữ liệu được lưu ở vùng nhớ đó) con trỏ rỗng thường (nhưng không phải luôn luôn) gây ra lỗi thời gian thực thi hoặc có thể khiến cho chương trình bị hư hại.

  • Trong ngôn ngữ lập trình C, hành vi tham chiếu ngược cho biến con trỏ rỗng chưa được định nghĩa.[5] Thực hiện phép toán này sẽ khiến cho chương trình bị dừng lại và gây nên lỗi phân đoạn, bởi vì việc sử dụng con trỏ rỗng để chỉ ra một địa chỉ không bao giờ được hệ thống cấp phát khu nhớ để lưu bất kỳ đối tượng nào. Tuy vậy, hành vi này không mang tính toàn cục.
  • Đối với ngôn ngữ lập trình Java, tham chiếu đến một đối tượng rỗng sẽ sinh ra ngoại lệ NullPointerException (NPE), ngoại lệ này có thể được bắt giữ xử lý qua mã, nhưng trong thực tiễn, phương pháp được ưu tiên là người lập trình cần phải tính toán cẩn thận để kiểu ngoại lệ này không bao giờ xảy ra.
  • Đối với môi trường.NET, tham chiếu đến một đối tượng rỗng sẽ phát sinh ngoại lệ NullReferenceException. Mặc dù bắt giữ ngoại lệ dạng này không được xem là cách thức lập trình tốt, chương trình vẫn có thể bắt và xử lý ngoại lệ này.
  • Trong ngôn ngữ Objective-C, thông tin có thể gởi tới một đối tượng nil (được hiểu là một con trỏ rỗng) mà không khiến cho chương trình phải dừng đột ngột; thông tin này đơn giản là được bỏ qua, giá trị trả về (nếu có) là nil hoặc 0, tùy vào kiểu dữ liệu.[6]

Lược sử

[sửa | sửa mã nguồn]

Năm 2009 C.A.R. Hoare cho biết[7][8] chính ông là người sáng tạo ra tham chiếu rỗng vào năm 1965 khi tạo ra ngôn ngữ lập trình Algol W, mặc dù NIL đã tồn tại trong Lisp kể từ năm 1959[cần dẫn nguồn]. Trong lần đề cập năm 2009 đó, Hoare nói rằng phát minh của ông là một "sai lầm tỉ đô":

Phải gọi đó là sai lầm tỉ đô của tôi. Phát minh về tham chiếu rỗng ra đời vào năm 1965. Vào thời điểm đó, tôi đang thiết kế hệ thống kiểu toàn diện đầu tiên dùng để tham chiếu cho ngôn ngữ lập trình hướng đối tượng (ALGOL W). Mục đích của tôi là muốn đoan chắc rằng việc sử dụng phép tham chiếu sẽ hoàn toàn an toàn, với việc kiểm tra được trình biên dịch thực hiện tự động. Nhưng tôi cũng không thể chối bỏ việc cám dỗ đặt vào trong đấy một tham chiếu rỗng, đơn giản là bởi vì thực hiện nó vô cùng dễ. Điều này là phát sinh vô số lỗi, lỗ hổng, và hệ thống bị đổ sập, đã tiêu tốn cả tỉ đô la vì những thiệt hại mà nó gây ra trong suốt bốn mươi năm.

Tham khảo

[sửa | sửa mã nguồn]
  1. ^ ISO/IEC 9899, clause 6.3.2.3, paragraph 4.
  2. ^ ISO/IEC 9899, clause 7.17, paragraph 3: NULL... which expands to an implementation-defined null pointer constant...
  3. ^ ISO/IEC 9899, clause 6.3.2.3, paragraph 3.
  4. ^ Stroustrup, Bjarne (tháng 3 năm 2001). “Chapter 5: Pointers, Arrays, and Structures: 5.1.1: Zero”. The C++ Programming Language (ấn bản thứ 14). United States and Canada: Addison–Wesley. tr. 88. ISBN 0-201-88954-4. In C, it has been popular to define a macro NULL to represent the zero pointer. Because of C++'s tighter type checking, the use of plain 0, rather than any suggested NULL macro, leads to fewer problems. If you feel you must define NULL. use
        const int NULL = 0;
    The const qualifier (§5.4) prevents accidental redefinition of NULL and ensures that NULL can be used where a constant is required.
  5. ^ ISO/IEC 9899, clause 6.5.3.2, paragraph 4.
  6. ^ The Objective-C 2.0 Programming Language, section "Sending Messages to nil".
  7. ^ Tony Hoare (2009). “Null References: The Billion Dollar Mistake”. QCon London. Bản gốc lưu trữ ngày 19 tháng 1 năm 2009. Truy cập ngày 22 tháng 1 năm 2017.
  8. ^ Tony Hoare (ngày 25 tháng 8 năm 2009). “Null References: The Billion Dollar Mistake”. InfoQ.com.

Liên kết ngoài

[sửa | sửa mã nguồn]
  • Joint Technical Committee ISO/IEC JTC 1, Subcommittee SC 22, Working Group WG 14 (8 tháng 9 năm 2007). International Standard ISO/IEC 9899 (PDF; Committee Draft).Quản lý CS1: nhiều tên: danh sách tác giả (liên kết).