Khi nào & tại sao con trỏ bắt đầu được xem là rủi ro?


18

Dường như đã có một sự thay đổi dần dần trong suy nghĩ về việc sử dụng con trỏ trong các ngôn ngữ lập trình sao cho nó thường được chấp nhận rằng con trỏ được coi là rủi ro (nếu không nói là "xấu xa" hoặc tương tự như vậy).

Những phát triển lịch sử là gì cho sự thay đổi trong suy nghĩ này? Đã có cụ thể, các sự kiện tinh dịch, nghiên cứu, hoặc phát triển khác?

Chẳng hạn, một cái nhìn hời hợt về quá trình chuyển đổi từ C sang C ++ sang Java dường như cho thấy một xu hướng bổ sung và sau đó thay thế hoàn toàn con trỏ bằng các tham chiếu. Tuy nhiên, chuỗi sự kiện thực sự có lẽ tinh tế & phức tạp hơn nhiều so với điều này, và gần như không theo trình tự. Các tính năng làm cho nó thành các ngôn ngữ chính có thể có nguồn gốc từ nơi khác, có lẽ từ lâu.

Lưu ý: Tôi không hỏi về giá trị thực tế của con trỏ so với tài liệu tham khảo so với cái gì khác. Tôi tập trung vào các lý do cho sự thay đổi rõ ràng này.


1
Đó là do sự suy giảm của giáo dục Nghệ thuật Tự do. Mọi người không còn có thể hiểu được Tham chiếu gián tiếp, một trong những ý tưởng cơ bản nhất trong công nghệ điện toán, được bao gồm trong tất cả các CPU.

10
Con trỏ rủi ro. Tại sao bạn nghĩ rằng đã có một sự thay đổi trong suy nghĩ? Đã có những cải tiến về tính năng ngôn ngữ và phần cứng cho phép viết phần mềm mà không cần con trỏ, mặc dù không phải không có một số hình phạt về hiệu suất.
Ngừng làm hại Monica

4
@DaveInCaz Theo tôi biết rằng sự phát triển cụ thể là phát minh ra con trỏ.
Ngừng làm hại Monica

5
@nocomprende: những gì bạn vừa viết không phải là sự thật cũng không phải là bằng chứng, chỉ là ý kiến. Có rất ít lập trình viên vào năm 1970, bạn không có bất kỳ bằng chứng nào cho thấy dân số ngày nay tốt hơn hay xấu hơn ở "tham chiếu gián tiếp".
whatsisname

3
Con trỏ luôn được coi là rủi ro, từ ngày đầu tiên. Nó chỉ đơn giản là một sự thỏa hiệp để chuyển chúng từ lắp ráp sang các ngôn ngữ cấp cao hơn.
Frank Hileman

Câu trả lời:


21

Lý do là sự phát triển của các lựa chọn thay thế cho con trỏ.

Trong phần mềm này, mọi con trỏ / tham chiếu / etc đang được triển khai dưới dạng một số nguyên chứa địa chỉ bộ nhớ (còn gọi là con trỏ). Khi C xuất hiện, chức năng này được đưa ra dưới dạng con trỏ. Điều này có nghĩa là bất cứ điều gì phần cứng cơ bản có thể làm để giải quyết bộ nhớ đều có thể được thực hiện bằng con trỏ.

Điều này luôn luôn là "nguy hiểm", nhưng nguy hiểm là tương đối. Khi bạn đang thực hiện chương trình 1000 dòng hoặc khi bạn có quy trình chất lượng phần mềm cấp IBM, mối nguy hiểm này có thể được giải quyết dễ dàng. Tuy nhiên, không phải tất cả các phần mềm đã được phát triển theo cách đó. Như vậy, một mong muốn cho các cấu trúc đơn giản hơn đã xuất hiện.

Nếu bạn nghĩ về nó, một int&int* constthực sự có cùng mức độ an toàn, nhưng một cái có cú pháp đẹp hơn nhiều so với cái khác. int&cũng có thể hiệu quả hơn vì nó có thể đề cập đến một int được lưu trong một thanh ghi (lỗi thời: điều này đúng trong quá khứ, nhưng các trình biên dịch hiện đại rất tốt trong việc tối ưu hóa đến mức bạn có thể có một con trỏ tới một số nguyên trong một thanh ghi, miễn là bạn không bao giờ sử dụng bất kỳ tính năng nào sẽ yêu cầu một địa chỉ thực tế, như ++)

Khi chúng tôi chuyển sang Java , chúng tôi chuyển sang các ngôn ngữ cung cấp một số đảm bảo bảo mật. CC ++ không cung cấp. Java đảm bảo rằng chỉ các hoạt động hợp pháp được thực thi. Để làm điều này, java đã bỏ đi hoàn toàn với con trỏ. Những gì họ tìm thấy là phần lớn các hoạt động con trỏ / tham chiếu được thực hiện trong mã thực là những thứ mà các tham chiếu là quá đủ cho. Chỉ trong một số ít trường hợp (chẳng hạn như lặp nhanh qua một mảng) là con trỏ thực sự cần thiết. Trong những trường hợp đó, java mất một thời gian chạy để tránh sử dụng chúng.

Việc di chuyển đã không được đơn điệu. Con trỏ giới thiệu lại C # , mặc dù ở dạng rất hạn chế. Chúng được đánh dấu là " không an toàn " , có nghĩa là chúng không thể được sử dụng bởi mã không tin cậy. Họ cũng có các quy tắc rõ ràng về những gì họ có thể và không thể chỉ ra (ví dụ: đơn giản là không hợp lệ để tăng một con trỏ qua cuối một mảng). Tuy nhiên, họ thấy có một số ít trường hợp cần hiệu suất cao của con trỏ, vì vậy họ đưa chúng trở lại.

Điều đáng quan tâm sẽ là các ngôn ngữ chức năng, vốn không có khái niệm nào như vậy, nhưng đó là một cuộc thảo luận rất khác.


3
Tôi không chắc rằng nó đúng khi nói rằng Java không có con trỏ. Tôi không muốn tranh luận lâu về cái gì và không phải là con trỏ nhưng JLS nói rằng "giá trị của tham chiếu là con trỏ". Không có quyền truy cập trực tiếp hoặc sửa đổi con trỏ được phép. Điều này cũng không chỉ để bảo mật, giúp mọi người tránh xa việc theo dõi vị trí của một đối tượng bất cứ lúc nào cũng có ích cho GC.
JimmyJames

6
@JimmyJames Đúng. Đối với mục đích của câu trả lời này, đường phân chia giữa con trỏ và con trỏ không là liệu nó có hỗ trợ các hoạt động số học của con trỏ hay không, thường không được hỗ trợ bởi các tham chiếu.
Cort Ammon - Phục hồi Monica

8
@JimmyJames Tôi đồng tình với khẳng định của Cort rằng một con trỏ là thứ mà bạn có thể thực hiện các phép toán số học, trong khi tham chiếu thì không. Cơ chế thực tế thực hiện một tham chiếu trong các ngôn ngữ như Java là một chi tiết triển khai.
Robert Harvey

3
Nói chung, C và C ++ đã tự nguyện chấp nhận tư cách thành viên của câu lạc bộ nguy hiểm này bằng cách cho phép nhiều "hành vi không xác định" vào đặc tả.
rwong

2
Bằng cách này, có CPU, mà phân biệt giữa con trỏ và số. Ví dụ, CPU CISC 48 bit gốc trong IBM AS / 400 thực hiện điều đó. Và trên thực tế, có một lớp trừu tượng bên dưới hệ điều hành, có nghĩa là không chỉ CPU phân biệt giữa con số và con trỏ và cấm số học trên con trỏ, nhưng hệ điều hành riêng của mình thậm chí không biết về con trỏ ở tất cả và không làm các ngôn ngữ . Thật thú vị, điều này làm cho hệ thống AS / 400 ban đầu trở thành một hệ thống, trong đó việc viết lại mã từ một ngôn ngữ kịch bản cấp cao trong C làm cho nó có độ lớn chậm hơn .
Jörg W Mittag

10

Một số loại gián tiếp là cần thiết cho các chương trình phức tạp (ví dụ cấu trúc dữ liệu đệ quy hoặc có kích thước thay đổi). Tuy nhiên, không cần thiết phải thực hiện điều hướng này thông qua con trỏ.

Phần lớn các ngôn ngữ lập trình cấp cao (không phải hội) là khá an toàn bộ nhớ và không cho phép truy cập con trỏ không bị hạn chế. Gia đình C là người kỳ quặc ở đây.

C phát triển ra khỏi B là một sự trừu tượng rất mỏng so với lắp ráp thô. B có một loại duy nhất: từ. Từ này có thể được sử dụng như một số nguyên hoặc như một con trỏ. Hai cái đó tương đương nhau khi toàn bộ bộ nhớ được xem như một mảng liền kề. C giữ cách tiếp cận khá linh hoạt này và tiếp tục hỗ trợ số học con trỏ vốn không an toàn. Toàn bộ hệ thống loại C là một suy nghĩ lại. Tính linh hoạt này đối với truy cập bộ nhớ khiến C rất phù hợp cho mục đích chính của nó: tạo mẫu cho hệ điều hành Unix. Tất nhiên Unix và C hóa ra khá phổ biến, do đó C cũng được sử dụng trong các ứng dụng mà cách tiếp cận bộ nhớ cấp thấp này không thực sự cần thiết.

Nếu chúng ta xem xét các ngôn ngữ lập trình xuất hiện trước C (ví dụ Fortran, phương ngữ Algol bao gồm Pascal, Cobol, Lisp, thì) một số ngôn ngữ hỗ trợ con trỏ giống C. Đáng chú ý, khái niệm con trỏ null được phát minh cho Algol W vào năm 1965. Nhưng không có ngôn ngữ nào cố gắng trở thành ngôn ngữ hệ thống trừu tượng thấp hiệu quả giống như C: Fortran có nghĩa là để tính toán khoa học, Algol đã phát triển một số khái niệm khá tiên tiến, Lisp là nhiều hơn một dự án nghiên cứu hơn là một ngôn ngữ cấp ngành và Cobol tập trung vào các ứng dụng kinh doanh.

Thu gom rác tồn tại từ cuối những năm 50, tức là trước C (đầu thập niên 70). GC yêu cầu an toàn bộ nhớ để hoạt động đúng. Ngôn ngữ trước và sau C sử dụng GC như một tính năng bình thường. Tất nhiên, điều đó làm cho một ngôn ngữ phức tạp hơn nhiều và có thể chậm hơn, đặc biệt đáng chú ý trong thời gian của các máy tính lớn. Các ngôn ngữ GC có xu hướng định hướng nghiên cứu (ví dụ Lisp, Simula, ML) và / hoặc yêu cầu các máy trạm mạnh mẽ (ví dụ Smalltalk).

Với các máy tính nhỏ hơn, mạnh hơn, nói chung và các ngôn ngữ GC nói riêng đã trở nên phổ biến hơn. Đối với các ứng dụng không theo thời gian thực (và đôi khi ngay cả sau đó), giờ đây, phương pháp được ưa thích. Nhưng thuật toán GC cũng là chủ đề của nghiên cứu mạnh mẽ. Thay vào đó, an toàn bộ nhớ tốt hơn mà không cần GC cũng đã được phát triển hơn nữa, đặc biệt là trong ba thập kỷ qua: những đổi mới đáng chú ý là RAII và con trỏ thông minh trong hệ thống kiểm tra vay / hệ thống suốt đời của C ++ và Rust.

Java đã không đổi mới bằng cách trở thành ngôn ngữ lập trình an toàn cho bộ nhớ: về cơ bản, nó đã sử dụng ngữ nghĩa của ngôn ngữ Smalltalk an toàn cho bộ nhớ và kết hợp chúng với cú pháp và gõ tĩnh của C ++. Sau đó, nó đã được bán trên thị trường như một C / C ++ tốt hơn, đơn giản hơn. Nhưng bề ngoài chỉ là một hậu duệ C ++. Việc thiếu con trỏ của Java bị nợ nhiều hơn đối với mô hình đối tượng Smalltalk hơn là từ chối mô hình dữ liệu C ++.

Vì vậy, không nên hiểu các ngôn ngữ hiện đại của Java như Java, Ruby và C # là khắc phục các vấn đề của con trỏ thô như trong C, mà nên được xem là vẽ từ nhiều truyền thống - bao gồm C, mà còn từ các ngôn ngữ an toàn hơn như Smalltalk, Simula, hoặc Lisp.


4

Theo kinh nghiệm của tôi, con trỏ luôn LUÔN là một khái niệm đầy thách thức đối với nhiều người. Vào năm 1970, trường đại học mà tôi đang theo học có B5500 Burroughs và chúng tôi đã sử dụng Extended Algol cho các dự án lập trình của mình. Kiến trúc phần cứng được dựa trên các mô tả và một số mã ở phần trên của các từ dữ liệu. Chúng được thiết kế rõ ràng để cho phép các mảng sử dụng các con trỏ mà không được phép bỏ qua phần cuối.

Chúng tôi đã thảo luận sôi nổi về các lớp học về tên và tham chiếu giá trị và cách các mảng B5500 hoạt động. Một số người trong chúng tôi đã giải thích ngay lập tức. Những người khác thì không.

Sau đó, có một chút sốc khi phần cứng không bảo vệ tôi khỏi các con trỏ chạy trốn - đặc biệt là trong ngôn ngữ lắp ráp. Trong công việc đầu tiên của tôi sau khi tốt nghiệp, tôi đã giúp khắc phục các sự cố trong hệ điều hành. Thường thì tài liệu duy nhất chúng tôi có là bãi rác in. Tôi đã phát triển một sở trường để tìm ra nguồn của các con trỏ chạy trốn trong các bãi chứa bộ nhớ, vì vậy mọi người đã đưa ra các bãi "không thể" cho tôi để tìm ra. Nhiều vấn đề chúng tôi đã gây ra do lỗi con trỏ hơn bất kỳ loại lỗi nào khác.

Nhiều người tôi đã làm việc bắt đầu viết FORTRAN, sau đó chuyển sang C, viết C rất giống với FORTRAN và tránh các con trỏ. Bởi vì chúng không bao giờ nội bộ con trỏ và tham chiếu, Java đặt ra vấn đề. Thông thường, thật khó để hiểu cho các lập trình viên FORTRAN cách phân công đối tượng thực sự hoạt động.

Các ngôn ngữ hiện đại đã giúp việc thực hiện những điều cần con trỏ "dưới mui xe" trở nên dễ dàng hơn rất nhiều trong khi vẫn giữ cho chúng ta an toàn khỏi lỗi chính tả và các lỗi khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.