Tôi đã suy nghĩ tại sao lại có (trong tất cả các ngôn ngữ lập trình mà tôi đã học, chẳng hạn như các thư viện chuẩn C ++, Java, Python) như stdlib, thay vì có các "hàm" tương tự là nguyên thủy của ngôn ngữ.
Tôi đã suy nghĩ tại sao lại có (trong tất cả các ngôn ngữ lập trình mà tôi đã học, chẳng hạn như các thư viện chuẩn C ++, Java, Python) như stdlib, thay vì có các "hàm" tương tự là nguyên thủy của ngôn ngữ.
Câu trả lời:
Cho phép tôi mở rộng phần nào câu trả lời hay của @ Vincent (+1) :
Tại sao trình biên dịch không thể đơn giản dịch một lệnh gọi hàm thành một bộ hướng dẫn?
Nó có thể, và làm như vậy thông qua ít nhất hai cơ chế:
nội tuyến một cuộc gọi chức năng - trong dịch, trình biên dịch có thể thay thế một cuộc gọi mã nguồn với việc thực hiện trực tiếp nội tuyến thay vì thực hiện cuộc gọi thực tế để hàm. Tuy nhiên, hàm cần phải có một triển khai được xác định ở đâu đó và có thể nằm trong thư viện chuẩn.
hàm nội tại - nội tại là các hàm mà trình biên dịch đã được thông báo mà không nhất thiết phải tìm thấy hàm trong thư viện. Chúng thường được dành riêng cho các tính năng phần cứng không thể truy cập thực tế theo bất kỳ cách nào khác, đơn giản đến mức ngay cả chi phí của chức năng thư viện ngôn ngữ lắp ráp cũng được coi là cao. (Trình biên dịch nói chung chỉ có thể tự động mã nguồn nội tuyến trong ngôn ngữ của nó, chứ không phải các hàm lắp ráp, đó là nơi cơ chế nội tại xuất hiện.)
Vẫn còn những điều này, tùy chọn tốt nhất đôi khi là cho trình biên dịch dịch một cuộc gọi hàm trong ngôn ngữ nguồn thành một cuộc gọi hàm trong mã máy. Đệ quy, phương thức ảo và kích thước tuyệt đối là một số lý do mà nội tuyến không phải lúc nào cũng có thể / thực tế. (Một lý do khác là mục đích của việc xây dựng, chẳng hạn như biên dịch riêng (mô-đun đối tượng), các đơn vị tải riêng biệt (ví dụ: DLL)).
Không có lợi thế thực sự nào để tạo ra hầu hết các chức năng thư viện tiêu chuẩn (điều đó sẽ mã hóa nhiều kiến thức hơn vào trình biên dịch mà không có lợi thế thực sự), do đó, việc gọi lại mã máy thường là phù hợp nhất.
C là một ngôn ngữ đáng chú ý có thể bỏ qua các câu lệnh ngôn ngữ rõ ràng khác có lợi cho các chức năng thư viện tiêu chuẩn. Mặc dù các thư viện đã tồn tại từ trước, ngôn ngữ này đã chuyển sang thực hiện nhiều công việc hơn từ các chức năng thư viện tiêu chuẩn và ít hơn là các tuyên bố rõ ràng trong ngữ pháp của ngôn ngữ. Chẳng hạn, IO trong các ngôn ngữ khác thường được đưa ra cú pháp riêng của mình dưới dạng các câu lệnh khác nhau, trong khi ngữ pháp C không định nghĩa bất kỳ câu lệnh IO nào, thay vào đó chỉ chuyển đến thư viện chuẩn của nó để cung cấp rằng, tất cả đều có thể truy cập thông qua các lệnh gọi hàm, trình biên dịch đã biết cách làm.
Điều này chỉ đơn giản là để giữ cho ngôn ngữ đơn giản nhất có thể. Bạn cần phân biệt giữa một tính năng của ngôn ngữ, chẳng hạn như một loại vòng lặp hoặc các cách để truyền tham số cho các hàm, v.v. và chức năng phổ biến mà hầu hết các ứng dụng cần.
Thư viện là các hàm có thể hữu ích cho nhiều lập trình viên, vì vậy chúng được tạo ra dưới dạng mã có thể sử dụng lại có thể được chia sẻ. Các thư viện tiêu chuẩn được thiết kế là các chức năng rất phổ biến mà các lập trình viên thường cần. Bằng cách này, ngôn ngữ lập trình ngay lập tức hữu ích cho nhiều lập trình viên hơn. Các thư viện có thể được cập nhật và mở rộng mà không thay đổi các tính năng cốt lõi của chính ngôn ngữ.
PHP
như một ví dụ hầu như không tạo ra bất kỳ sự khác biệt nào giữa các chức năng ngôn ngữ rộng lớn và chính ngôn ngữ đó.
include
, require
và require_once
, nếu / cho / while (lập trình có cấu trúc), trường hợp ngoại lệ, một hệ thống riêng biệt của 'giá trị lỗi', phức tạp quy tắc gõ yếu, phức tạp quy tắc điều hành ưu tiên, và cứ tiếp tục . So sánh điều này với sự đơn giản của, ví dụ, Smalltalk, Scheme, Prolog, Forth, v.v.)
Ngoài những gì các câu trả lời khác đã nói, việc đưa các chức năng tiêu chuẩn vào thư viện là sự phân tách các mối quan tâm :
Đó là công việc của nhà biên dịch để phân tích ngôn ngữ và tạo mã cho nó. Đây không phải là công việc của nhà soạn nhạc để chứa bất cứ thứ gì có thể được viết bằng ngôn ngữ đó và được cung cấp dưới dạng thư viện.
Đó là công việc của thư viện tiêu chuẩn (công việc luôn luôn có sẵn) để cung cấp chức năng cốt lõi cần thiết cho hầu hết các chương trình. Đây không phải là công việc của thư viện chuẩn để chứa tất cả các chức năng có thể hữu ích.
Đây là công việc của các thư viện tiêu chuẩn tùy chọn để cung cấp chức năng phụ trợ mà nhiều chương trình có thể làm mà không cần, nhưng vẫn khá cơ bản và cũng cần thiết cho nhiều ứng dụng để đảm bảo vận chuyển với môi trường tiêu chuẩn. Đây không phải là công việc của những thư viện tùy chọn đó để chứa tất cả các mã có thể sử dụng lại được viết.
Đó là công việc của các thư viện người dùng để cung cấp các bộ sưu tập các chức năng tái sử dụng hữu ích. Đây không phải là công việc của các thư viện người dùng để chứa tất cả các mã đã được viết.
Đó là công việc của mã nguồn của ứng dụng để cung cấp các bit mã còn lại thực sự chỉ liên quan đến một ứng dụng đó.
Nếu bạn muốn một phần mềm phù hợp với một kích thước, bạn sẽ có được thứ gì đó cực kỳ phức tạp. Bạn cần phải mô đun hóa để giảm độ phức tạp xuống mức có thể quản lý được. Và bạn cần phải mô đun hóa để cho phép thực hiện một phần :
Thư viện luồng không có giá trị trên bộ điều khiển nhúng đơn lõi. Cho phép thực hiện ngôn ngữ cho bộ điều khiển nhúng này để không bao gồm pthread
thư viện là điều nên làm.
Thư viện toán học không có giá trị trên bộ điều khiển vi mô thậm chí không có FPU. Một lần nữa, không bị buộc phải cung cấp các chức năng như sin()
làm cho cuộc sống dễ dàng hơn nhiều đối với những người triển khai ngôn ngữ của bạn cho bộ điều khiển vi mô đó.
Ngay cả thư viện tiêu chuẩn cốt lõi cũng vô dụng khi bạn đang lập trình kernel. Bạn không thể thực hiện write()
mà không có một tòa nhà vào kernel và bạn không thể thực hiện printf()
mà không có write()
. Là một lập trình viên hạt nhân, công việc của bạn là cung cấp tòa nhà write()
, bạn không thể mong đợi nó ở đó.
Một ngôn ngữ không cho phép thiếu sót như vậy từ các thư viện tiêu chuẩn đơn giản là không phù hợp với nhiều nhiệm vụ . Nếu bạn muốn ngôn ngữ của mình có thể sử dụng linh hoạt trong các môi trường không phổ biến, nó phải linh hoạt trong những thư viện chuẩn được bao gồm. Ngôn ngữ của bạn càng dựa vào các thư viện chuẩn, thì càng có nhiều giả định về môi trường thực thi của nó và do đó hạn chế sử dụng đối với các môi trường cung cấp các điều kiện tiên quyết này.
Tất nhiên, các ngôn ngữ cấp cao như python và java có thể đưa ra rất nhiều giả định về môi trường của chúng. Và họ có xu hướng bao gồm nhiều, rất nhiều thứ vào thư viện tiêu chuẩn của họ. Các ngôn ngữ cấp thấp hơn như C cung cấp ít hơn nhiều trong các thư viện tiêu chuẩn của họ và giữ cho thư viện tiêu chuẩn cốt lõi nhỏ hơn nhiều. Đó là lý do tại sao bạn tìm thấy một trình biên dịch C hoạt động cho hầu hết mọi kiến trúc, nhưng có thể không chạy được bất kỳ tập lệnh python nào trên nó.
Một lý do lớn khiến trình biên dịch và thư viện chuẩn tách biệt là vì chúng phục vụ hai mục đích khác nhau (ngay cả khi cả hai đều được xác định bởi cùng một thông số ngôn ngữ): trình biên dịch dịch mã cấp cao hơn thành hướng dẫn máy và thư viện chuẩn cung cấp thử nghiệm trước thực hiện các chức năng thường cần thiết. Trình biên dịch giá trị mô đun hóa giống như các nhà phát triển phần mềm khác làm. Trong thực tế, một số trình biên dịch C đầu tiên tiếp tục phân tách trình biên dịch thành các chương trình riêng biệt để xử lý trước, biên dịch và liên kết.
Mô-đun này cung cấp cho bạn một loạt các lợi thế:
Về mặt lịch sử (ít nhất là theo quan điểm của C), các phiên bản gốc, chuẩn hóa của ngôn ngữ hoàn toàn không có thư viện chuẩn. Các nhà cung cấp hệ điều hành và bên thứ ba thường sẽ cung cấp các thư viện đầy đủ chức năng thường được sử dụng, nhưng các triển khai khác nhau bao gồm những thứ khác nhau và chúng hầu như không tương thích với nhau. Khi C được chuẩn hóa, họ đã định nghĩa một "thư viện chuẩn" trong nỗ lực hài hòa các triển khai khác nhau này và cải thiện tính di động. Thư viện chuẩn C được phát triển tách biệt với ngôn ngữ, giống như các thư viện Boost dành cho C ++, nhưng sau đó được tích hợp vào đặc tả ngôn ngữ.
Câu trả lời góc bổ sung: Quản lý tài sản trí tuệ
Ví dụ đáng chú ý là việc triển khai Math.Pow (gấp đôi, gấp đôi) trong .NET Framework được Microsoft mua từ Intel và vẫn chưa được tiết lộ ngay cả khi khung này là nguồn mở. (Nói chính xác, trong trường hợp trên, đó là một cuộc gọi nội bộ chứ không phải là thư viện nhưng ý tưởng vẫn giữ.) Một thư viện tách biệt với ngôn ngữ (về mặt lý thuyết cũng là một tập hợp con của các thư viện chuẩn) có thể giúp người ủng hộ ngôn ngữ linh hoạt hơn trong việc vẽ ranh giới giữa những gì cần giữ minh bạch và những gì phải được tiết lộ (do hợp đồng của họ với bên thứ 3 hoặc các lý do liên quan đến IP khác).
Math.Pow
không đề cập đến bất kỳ giao dịch mua nào, hoặc bất cứ điều gì về Intel và nói về những người đọc mã nguồn của việc triển khai chức năng.
Lỗi và gỡ lỗi.
Lỗi: Tất cả phần mềm đều có lỗi, thư viện chuẩn của bạn có lỗi và trình biên dịch của bạn có lỗi. Là người sử dụng ngôn ngữ, việc tìm và khắc phục các lỗi như vậy sẽ dễ dàng hơn nhiều khi chúng ở trong thư viện chuẩn trái ngược với trình biên dịch.
Gỡ lỗi: Tôi dễ dàng hơn nhiều khi thấy dấu vết ngăn xếp của thư viện chuẩn và cho tôi cảm giác về những gì có thể xảy ra. Bởi vì dấu vết ngăn xếp đó có mã tôi hiểu. Tất nhiên, bạn có thể đào sâu hơn và bạn cũng có thể theo dõi các chức năng nội tại của mình, nhưng sẽ dễ dàng hơn rất nhiều nếu đó là ngôn ngữ bạn sử dụng mọi lúc từ ngày này sang ngày khác.
Đây là một câu hỏi tuyệt vời!
Ví dụ, Tiêu chuẩn C ++ không bao giờ chỉ định những gì nên được thực hiện trong trình biên dịch hoặc trong thư viện chuẩn: nó chỉ đề cập đến việc thực hiện . Ví dụ, các ký hiệu dành riêng được xác định bởi cả trình biên dịch (dưới dạng nội tại) và bởi thư viện chuẩn, có thể thay thế cho nhau.
Tuy nhiên, tất cả các triển khai C ++ mà tôi biết sẽ có số lượng nội tại tối thiểu có thể được cung cấp bởi trình biên dịch và càng nhiều càng tốt do thư viện chuẩn cung cấp.
Do đó, mặc dù về mặt kỹ thuật có thể xác định thư viện chuẩn là chức năng nội tại trong trình biên dịch, nó dường như hiếm khi được sử dụng trong thực tế.
Chúng ta hãy xem xét ý tưởng di chuyển một số chức năng từ thư viện tiêu chuẩn sang trình biên dịch.
Ưu điểm:
Nhược điểm:
std
) trở nên khó khăn hơn để thử nghiệm.Điều này có nghĩa là việc chuyển một cái gì đó sang trình biên dịch là tốn kém , hiện tại và trong tương lai, và do đó nó đòi hỏi một trường hợp chắc chắn. Đối với một số phần chức năng, nó là cần thiết (chúng không thể được viết dưới dạng mã thông thường), tuy nhiên sau đó nó phải trả tiền để trích xuất các phần tối thiểu và chung chung để chuyển đến trình biên dịch và xây dựng chúng trong thư viện chuẩn.
Là một nhà thiết kế ngôn ngữ, tôi muốn lặp lại một số câu trả lời khác ở đây, nhưng cung cấp nó qua con mắt của một người đang xây dựng ngôn ngữ.
Một API chưa kết thúc khi bạn hoàn thành việc thêm mọi thứ bạn có thể vào nó. Một API kết thúc khi bạn hoàn thành việc lấy mọi thứ bạn có thể ra khỏi nó.
Một ngôn ngữ lập trình phải được chỉ định bằng cách sử dụng một số ngôn ngữ. Bạn phải có khả năng truyền đạt ý nghĩa đằng sau bất kỳ chương trình nào được viết bằng ngôn ngữ của bạn. Ngôn ngữ này rất khó viết, và thậm chí khó viết hơn. Nói chung, nó có xu hướng là một dạng tiếng Anh có cấu trúc rất chính xác và được sử dụng để truyền đạt ý nghĩa không phải cho máy tính, mà cho các nhà phát triển khác, đặc biệt là những nhà phát triển viết trình biên dịch hoặc phiên dịch cho ngôn ngữ của bạn. Đây là một ví dụ từ thông số C ++ 11, [intro.multithread / 14]:
Trình tự tác dụng phụ có thể nhìn thấy trên một đối tượng nguyên tử M, liên quan đến tính toán giá trị B của M, là một chuỗi tác dụng phụ tiếp giáp tối đa theo thứ tự sửa đổi của M, trong đó hiệu ứng phụ đầu tiên có thể nhìn thấy đối với B và đối với mọi tác dụng phụ, không phải là trường hợp B xảy ra trước nó. Giá trị của một đối tượng nguyên tử M, như được xác định bởi đánh giá B, sẽ là giá trị được lưu trữ bởi một số thao tác trong chuỗi hiển thị của M đối với B. [Lưu ý: Có thể thấy rằng chuỗi tác dụng phụ có thể nhìn thấy của một giá trị tính toán là duy nhất cho các yêu cầu kết hợp dưới đây. Lưu ý
Chảy máu! Bất cứ ai đã tham gia tìm hiểu cách C ++ 11 xử lý đa luồng đều có thể đánh giá cao lý do tại sao từ ngữ này lại quá mờ đục, nhưng điều đó không tha thứ cho sự thật rằng nó ... quá ... mờ đục!
Tương phản với định nghĩa của std::shared_ptr<T>::reset
, trong phần thư viện của tiêu chuẩn:
template <class Y> void reset(Y* p);
Tác dụng: Tương đương với
shared_ptr(p).swap(*this)
Vậy sự khác biệt là gì? Trong phần định nghĩa ngôn ngữ, các nhà văn không thể cho rằng người đọc hiểu được nguyên thủy ngôn ngữ. Tất cả mọi thứ phải được chỉ định cẩn thận trong văn xuôi tiếng Anh. Khi chúng ta đến phần định nghĩa thư viện, chúng ta có thể sử dụng ngôn ngữ để chỉ định hành vi. Điều này thường dễ dàng hơn nhiều!
Về nguyên tắc, người ta có thể có một quá trình xây dựng suôn sẻ từ các nguyên thủy khi bắt đầu tài liệu đặc tả, cho đến khi xác định những gì chúng ta sẽ nghĩ là "các tính năng thư viện chuẩn", mà không phải vẽ một đường giữa "nguyên thủy ngôn ngữ" và Tính năng "thư viện chuẩn". Trong thực tế, dòng đó chứng tỏ giá trị rất lớn để vẽ bởi vì nó cho phép bạn viết một số phần phức tạp nhất của ngôn ngữ (chẳng hạn như những phần phải thực hiện thuật toán) bằng ngôn ngữ được thiết kế để diễn đạt chúng.
Và chúng tôi thực sự thấy một số dòng mờ:
java.lang.ref.Reference<T>
có thể chỉ được subclassed bởi các lớp thư viện chuẩn java.lang.ref.WeakReference<T>
java.lang.ref.SoftReference<T>
và java.lang.ref.PhantomReference<T>
vì những hành vi của Reference
bị để gắn chặt sâu sắc với các đặc điểm kỹ thuật ngôn ngữ Java rằng họ cần phải đặt một số hạn chế thành phần của quá trình đó thực hiện như "thư viện chuẩn" các lớp học.Điều này có nghĩa là một bổ sung cho các câu trả lời hiện có (và quá dài cho một nhận xét).
Có ít nhất hai lý do khác cho một thư viện chuẩn:
Nếu một tính năng ngôn ngữ cụ thể có trong một chức năng thư viện và tôi muốn biết nó hoạt động như thế nào, tôi chỉ có thể đọc nguồn cho chức năng đó. Nếu tôi muốn gửi một báo cáo lỗi / yêu cầu vá / kéo, nói chung không quá khó để viết mã (các) trường hợp sửa lỗi. Nếu nó nằm trong trình biên dịch, tôi phải có khả năng đào sâu vào bên trong. Ngay cả khi nó có cùng ngôn ngữ (và nó phải như vậy, bất kỳ trình biên dịch tự tôn nào cũng nên tự lưu trữ) mã trình biên dịch không giống như mã ứng dụng. Nó có thể mất mãi mãi để thậm chí tìm thấy các tập tin chính xác.
Bạn đang tự cắt đứt với rất nhiều người đóng góp tiềm năng nếu bạn đi theo con đường đó.
Nhiều ngôn ngữ cung cấp tính năng này ở mức độ này hay mức độ khác, nhưng sẽ rất phức tạp khi tải lại mã đang thực hiện tải lại nóng. Nếu SL tách biệt với thời gian chạy, nó có thể được tải lại.
Đây là một câu hỏi thú vị nhưng đã có nhiều câu trả lời hay, vì vậy tôi sẽ không thử hoàn thành.
Tuy nhiên, hai điều mà tôi không nghĩ đã nhận được đủ sự chú ý:
Đầu tiên là toàn bộ điều không phải là siêu rõ ràng. Đó là một chút phổ chính xác bởi vì có những lý do để làm những điều khác biệt. Ví dụ, trình biên dịch thường biết về các thư viện chuẩn và các chức năng của chúng. Ví dụ về ví dụ: Hàm "Hello World" của C - printf - là chức năng tốt nhất tôi có thể nghĩ đến. Đó là một chức năng thư viện, nó phải được sắp xếp, vì nó phụ thuộc rất nhiều vào nền tảng. Nhưng hành vi của nó (triển khai được xác định) cần phải được trình biên dịch biết để cảnh báo cho lập trình viên về các lệnh xấu. Điều này không đặc biệt gọn gàng, nhưng được coi là một sự thỏa hiệp tốt. Ngẫu nhiên, đây là câu trả lời thực sự cho hầu hết các câu hỏi "tại sao thiết kế này": rất nhiều thỏa hiệp và "dường như là một ý tưởng tốt vào thời điểm đó". Không phải lúc nào cũng "đây là cách rõ ràng để làm điều đó" hoặc "
Thứ hai là nó cho phép thư viện tiêu chuẩn không phải là tất cả tiêu chuẩn đó. Có rất nhiều tình huống mà một ngôn ngữ là mong muốn nhưng các thư viện tiêu chuẩn thường đi kèm với chúng không phải là thực tế và mong muốn. Đây là trường hợp phổ biến nhất với các ngôn ngữ lập trình hệ thống như C, trên các nền tảng không chuẩn. Ví dụ: nếu bạn có một hệ thống không có HĐH hoặc bộ lập lịch: bạn sẽ không có luồng.
Với một mô hình thư viện chuẩn (và luồng được hỗ trợ trong nó), điều này có thể được xử lý sạch: trình biên dịch khá giống nhau, bạn có thể sử dụng lại các bit của các thư viện áp dụng và bất cứ thứ gì bạn không thể loại bỏ. Nếu điều này được đưa vào trình biên dịch, mọi thứ bắt đầu trở nên lộn xộn.
Ví dụ:
Bạn không thể là một trình biên dịch tuân thủ.
Làm thế nào bạn sẽ chỉ ra độ lệch của bạn từ tiêu chuẩn. Lưu ý thường có một số hình thức nhập / bao gồm cú pháp mà bạn có thể đã thất bại, ví dụ như nhập của pythons hoặc C bao gồm dễ dàng chỉ ra vấn đề nếu có bất cứ điều gì thiếu trong mô hình thư viện chuẩn.
Ngoài ra các vấn đề tương tự cũng được áp dụng nếu bạn muốn điều chỉnh hoặc mở rộng chức năng của 'thư viện'. Điều này là phổ biến hơn nhiều so với bạn nghĩ. Chỉ cần gắn bó với luồng: windows, linux và một số đơn vị xử lý mạng kỳ lạ, tất cả đều thực hiện luồng hoàn toàn khác nhau. Mặc dù các bit linux / windows có thể khá tĩnh và có thể sử dụng một API giống hệt nhau, nhưng công cụ NPU sẽ thay đổi theo ngày trong tuần và API với nó. Trình biên dịch sẽ nhanh chóng đi chệch hướng khi mọi người quyết định những bit nào họ cần hỗ trợ / có thể thực hiện được khá nhanh nếu không có cách nào để tách loại điều này ra.