C ++: Thiếu tiêu chuẩn hóa ở cấp độ nhị phân


14

Tại sao ISO / ANSI không chuẩn hóa C ++ ở cấp nhị phân? Có nhiều vấn đề về tính di động với C ++, điều này chỉ do thiếu tiêu chuẩn hóa ở cấp độ nhị phân.

Don Box viết, (trích từ cuốn sách Essential COM của anh ấy , chương COM As A Better C ++ )

C ++ và tính di động


Một khi quyết định được đưa ra để phân phối một lớp C ++ dưới dạng DLL, người ta sẽ phải đối mặt với một trong những điểm yếu cơ bản của C ++ , đó là thiếu tiêu chuẩn hóa ở cấp độ nhị phân . Mặc dù Tài liệu Dự thảo ISO / ANSI C ++ cố gắng mã hóa các chương trình nào sẽ biên dịch và các hiệu ứng ngữ nghĩa của việc chạy chúng sẽ là gì, nhưng nó không cố gắng chuẩn hóa mô hình thời gian chạy nhị phân của C ++. Lần đầu tiên vấn đề này trở nên rõ ràng là khi khách hàng cố gắng liên kết với thư viện nhập khẩu của FastString DLL từ môi trường phát triển C ++ khác với môi trường được sử dụng để xây dựng DLL FastString.

Có nhiều lợi ích hơn hay mất sự thiếu tiêu chuẩn nhị phân này?


Đây có phải là câu hỏi tốt hơn trên lập trình viên.stackexchange.com , xem nó như thế nào nhiều hơn một câu hỏi chủ quan?
Stephen Furlani

1
Câu hỏi liên quan của tôi thực sự: stackoverflow.com/questions/2083060/
Kẻ

4
Don Box là một người nhiệt tâm. Mặc kệ anh.
John Dibling

8
Chà, C cũng không được chuẩn hóa bởi ANSI / ISO ở cấp độ nhị phân; OTOH C có một de facto tiêu chuẩn ABI chứ không phải là de jure một. C ++ không có ABI được tiêu chuẩn hóa như vậy bởi vì các nhà sản xuất khác nhau có các mục tiêu khác nhau với việc triển khai của họ. Ví dụ: các trường hợp ngoại lệ trong cõng VC ++ trên Windows SEH. POSIX không có SEH và do đó, việc sử dụng mô hình đó sẽ không có ý nghĩa (Vì vậy, G ++ và MinGW không sử dụng mô hình đó).
Billy ONeal

3
Tôi thấy đây là một tính năng không phải là một điểm yếu. Nếu bạn ràng buộc triển khai với một ABI cụ thể thì chúng tôi sẽ không bao giờ có sự đổi mới và phần cứng mới sẽ bị ràng buộc với thiết kế ngôn ngữ (và vì có 15 năm giữa mỗi phiên bản mới tồn tại lâu trong ngành phần cứng) và bằng cách ngột ngạt đổi mới ý tưởng mới để làm cho mã thực thi hiệu quả hơn sẽ không được thực hiện. Giá là tất cả các mã trong một tệp thực thi phải được xây dựng bởi cùng một trình biên dịch / phiên bản (một vấn đề nhưng không phải là chính).

Câu trả lời:


16

Các ngôn ngữ có dạng biên dịch tương thích nhị phân là một giai đoạn tương đối mới [*], ví dụ như thời gian chạy của JVM và .NET. Trình biên dịch C và C ++ thường phát ra mã gốc.

Ưu điểm là không cần JIT, hoặc trình thông dịch mã byte, hoặc VM, hoặc bất kỳ thứ gì khác như vậy. Ví dụ: bạn không thể viết mã bootstrap chạy khi khởi động máy dưới dạng mã byte Java di động, đẹp mắt, trừ khi có lẽ máy có thể thực thi mã byte Java hoặc bạn có một số loại trình chuyển đổi từ Java sang nguồn gốc không tương thích nhị phân mã thực thi (về lý thuyết: không chắc chắn điều này có thể được khuyến nghị trong thực tế cho mã bootstrap). Bạn có thể viết nó bằng C ++, ít nhiều, mặc dù không phải C ++ di động ngay cả ở cấp nguồn, vì nó sẽ gây ra nhiều rắc rối với các địa chỉ phần cứng ma thuật.

Nhược điểm là tất nhiên mã gốc chỉ chạy hoàn toàn trên kiến ​​trúc mà nó được biên dịch và các tệp thực thi chỉ có thể được tải bởi trình tải hiểu định dạng thực thi của chúng và chỉ liên kết với và gọi vào các tệp thực thi khác cho cùng kiến ​​trúc ABI.

Ngay cả khi bạn đã đi xa đến thế, việc liên kết hai tệp thực thi với nhau sẽ chỉ thực sự hoạt động chính xác miễn là: (a) bạn không vi phạm Quy tắc Một định nghĩa, rất dễ thực hiện nếu chúng được biên dịch với các trình biên dịch / tùy chọn / bất cứ thứ gì khác, sao cho họ đang sử dụng các định nghĩa khác nhau của cùng một lớp (hoặc trong một tiêu đề hoặc bởi vì mỗi định nghĩa được liên kết tĩnh với các triển khai khác nhau); và (b) tất cả các chi tiết triển khai có liên quan như bố cục cấu trúc là giống hệt nhau theo các tùy chọn trình biên dịch có hiệu lực khi từng được biên dịch.

Đối với tiêu chuẩn C ++ để xác định tất cả điều này sẽ loại bỏ rất nhiều quyền tự do hiện có cho người thực hiện. Các nhà triển khai đang sử dụng các quyền tự do đó, đặc biệt là khi viết mã cấp độ rất thấp trong C ++ (và C, có cùng một vấn đề).

Nếu bạn muốn viết một cái gì đó trông hơi giống C ++, đối với mục tiêu di động nhị phân, có C ++ / CLI, nhắm mục tiêu .NET và Mono để bạn có thể (hy vọng) chạy .NET ở nơi khác ngoài Windows. Tôi nghĩ có thể thuyết phục trình biên dịch của MS tạo ra các hội đồng CIL thuần túy sẽ chạy trên Mono.

Cũng có những thứ có khả năng có thể được thực hiện với ví dụ LLVM để tạo môi trường C hoặc C ++ nhị phân di động. Tuy nhiên, tôi không biết rằng bất kỳ ví dụ phổ biến nào đã xuất hiện.

Nhưng tất cả đều dựa vào việc sửa chữa rất nhiều thứ mà C ++ làm cho phụ thuộc vào việc triển khai (chẳng hạn như kích thước của các loại). Sau đó, môi trường hiểu các nhị phân di động, phải có sẵn trên hệ thống nơi mã được chạy. Bằng cách cho phép các tệp nhị phân không di động, C và C ++ có thể đi đến những nơi mà các tệp nhị phân di động không thể, và đó là lý do tại sao tiêu chuẩn không nói gì về nhị phân.

Sau đó, trên bất kỳ nền tảng cụ thể nào, việc triển khai thường vẫn không cung cấp khả năng tương thích nhị phân giữa các bộ tùy chọn khác nhau, mặc dù tiêu chuẩn không dừng chúng. Nếu Don Box không thích trình biên dịch của Microsoft có thể tạo ra các nhị phân không tương thích từ cùng một nguồn, theo các tùy chọn của trình biên dịch, thì đó là nhóm trình biên dịch mà anh ta cần phải phàn nàn. Ngôn ngữ C ++ không cấm trình biên dịch hoặc HĐH ghim tất cả các chi tiết cần thiết, vì vậy một khi bạn giới hạn bản thân mình trong Windows thì đó không phải là vấn đề cơ bản với C ++. Microsoft đã chọn không làm như vậy.

Sự khác biệt thường biểu hiện như một điều nữa mà bạn có thể hiểu sai và làm hỏng chương trình của mình, nhưng có thể có những lợi ích đáng kể để đạt được hiệu quả giữa, ví dụ, gỡ lỗi không tương thích so với các phiên bản phát hành của một dll.

[*] Tôi không chắc chắn khi ý tưởng được phát minh lần đầu tiên, có thể là 1642 hoặc một cái gì đó, nhưng mức độ phổ biến hiện tại của chúng là tương đối mới, so với thời điểm C ++ cam kết với các quyết định thiết kế ngăn nó xác định tính di động nhị phân.


@Steve Nhưng C có ABI được xác định rõ trên i386 và AMD64, vì vậy tôi có thể chuyển một con trỏ tới hàm do GCC phiên bản X biên dịch sang hàm được biên dịch bởi MSVC phiên bản Y. Không thể thực hiện điều đó với chức năng C ++.
dùng877329

7

Khả năng tương thích đa nền tảng và trình biên dịch chéo không phải là mục tiêu chính đằng sau C và C ++. Chúng được sinh ra trong một thời đại, và được dự định cho các mục đích mà việc tối thiểu hóa thời gian và không gian dành riêng cho trình biên dịch cụ thể là rất quan trọng.

Từ "Thiết kế và tiến hóa của C ++" của Stroustrup:

"Mục đích rõ ràng là phù hợp với C về thời gian chạy, độ nén mã và độ nén dữ liệu. ... Lý tưởng - đã đạt được - là C với Class có thể được sử dụng cho bất cứ điều gì C có thể được sử dụng cho."


1
+1 - chính xác. Làm thế nào một người có thể xây dựng một ABI tiêu chuẩn hoạt động trên cả hai hộp ARM và Intel? Sẽ không có ý nghĩa!
Billy ONeal

1
Thật không may, nó đã thất bại trong việc này. Bạn có thể làm mọi thứ C làm ... ngoại trừ tải động mô-đun C ++ khi chạy. bạn phải 'hoàn nguyên' để sử dụng các hàm C trong giao diện được hiển thị.
gbjbaanb

6

Nó không phải là một lỗi, đó là một tính năng! Điều này cho phép người thực hiện tự do để tối ưu hóa việc thực hiện của họ ở cấp nhị phân. I386 endian nhỏ và con đẻ của nó không phải là CPU duy nhất tồn tại hoặc tồn tại.


6

Vấn đề được mô tả trong trích dẫn là do việc tránh tiêu chuẩn hóa các sơ đồ xáo trộn tên biểu tượng (tôi nghĩ rằng " tiêu chuẩn hóa ở cấp nhị phân " là một cụm từ gây hiểu lầm về mặt này mặc dù vấn đề này liên quan đến Giao diện nhị phân của ứng dụng ( ABI).

C ++ mã hóa thông tin chữ ký và kiểu của đối tượng dữ liệu và thành viên lớp / không gian tên của nó thành tên biểu tượng và các trình biên dịch khác nhau được phép sử dụng các lược đồ khác nhau. Do đó, một biểu tượng trong thư viện tĩnh, tệp DLL hoặc tệp đối tượng sẽ không liên kết với mã được biên dịch bằng trình biên dịch khác (hoặc thậm chí có thể là một phiên bản khác của cùng một trình biên dịch).

Vấn đề được mô tả và giải thích có lẽ tốt hơn tôi có thể ở đây , với các ví dụ về các lược đồ được sử dụng bởi các trình biên dịch khác nhau.

Những lý do cho việc thiếu tiêu chuẩn hóa có chủ ý cũng được giải thích ở đây .


3

Mục đích của ISO / ANSI là chuẩn hóa ngôn ngữ C ++, vấn đề dường như đủ phức tạp để yêu cầu nhiều năm để có bản cập nhật các tiêu chuẩn ngôn ngữ và hỗ trợ trình biên dịch.

Khả năng tương thích nhị phân phức tạp hơn nhiều, do các nhị phân cần chạy trên các kiến ​​trúc CPU khác nhau và các môi trường HĐH khác nhau.


Đúng, nhưng vấn đề được mô tả trong trích dẫn trên thực tế không liên quan gì đến "khả năng tương thích mức nhị phân" (mặc dù sử dụng thuật ngữ của tác giả) theo bất kỳ ý nghĩa nào khác ngoài những điều như vậy được định nghĩa trong một "Giao diện nhị phân ứng dụng". Trong thực tế, ông đang mô tả vấn đề của các kế hoạch xáo trộn tên không tương thích.

@Clifford: sơ đồ xáo trộn tên chỉ là một tập hợp con của khả năng tương thích cấp nhị phân. cái sau giống như một thuật ngữ ô!
Nawaz

Tôi nghi ngờ có vấn đề với việc cố gắng chạy nhị phân Linux trên máy tính windows. Mọi thứ sẽ tốt hơn rất nhiều nếu có một nền tảng ABI, vì ít nhất một ngôn ngữ kịch bản có thể tự động tải và chạy nhị phân trên cùng một nền tảng hoặc các ứng dụng có thể sử dụng các thành phần được xây dựng với trình biên dịch khác. Bạn không thể sử dụng một dll C trên linux ngày hôm nay và không ai phàn nàn, nhưng C dll đó vẫn có thể được tải bởi một ứng dụng python, nơi lợi ích tích lũy.
gbjbaanb

2

Như Andy nói khả năng tương thích đa nền tảng không phải là một mục tiêu lớn, trong khi việc triển khai phần cứng và nền tảng rộng lớn là một mục tiêu, với kết quả cuối cùng là bạn có thể viết các triển khai tuân thủ cho nhiều lựa chọn hệ thống. Tiêu chuẩn hóa nhị phân sẽ làm cho điều này thực tế không thể thực hiện được.

Khả năng tương thích C cũng rất quan trọng và sẽ phức tạp đáng kể điều này.

Sau đó, đã có một số nỗ lực để chuẩn hóa ABI cho một tập hợp con các triển khai.


Chết tiệt, tôi quên mất khả năng tương thích C. Điểm tốt, +1!
Andy Thomas

1

Tôi nghĩ rằng việc thiếu một tiêu chuẩn cho C ++ là một vấn đề trong thế giới lập trình mô đun, ghép nối ngày nay. Tuy nhiên, chúng ta phải xác định những gì chúng ta muốn từ một tiêu chuẩn như vậy.

Không ai trong tâm trí của họ muốn xác định việc triển khai hoặc nền tảng cho nhị phân. Vì vậy, bạn không thể lấy dll Windows x86 và bắt đầu sử dụng nó trên nền tảng Linux x86_64. Đó sẽ là một chút nhiều.

Tuy nhiên, những gì mọi người muốn là giống như những gì chúng ta có với các mô-đun C - một giao diện được tiêu chuẩn hóa ở cấp nhị phân (tức là một khi được biên dịch). Hiện tại, nếu bạn muốn tải một dll trong một ứng dụng mô-đun, bạn xuất các hàm C và liên kết với chúng khi chạy. Bạn không thể làm điều đó với một mô-đun C ++. Sẽ thật tuyệt nếu bạn có thể, điều đó cũng có nghĩa là các dll được viết bằng một trình biên dịch có thể được tải bởi một trình biên dịch khác. Chắc chắn, bạn vẫn không thể tải một dll được xây dựng cho một nền tảng không tương thích, nhưng đó không phải là vấn đề cần khắc phục.

Vì vậy, nếu phần tiêu chuẩn xác định giao diện mà mô-đun hiển thị, thì chúng tôi sẽ linh hoạt hơn rất nhiều trong việc tải các mô-đun C ++, chúng tôi sẽ không phải hiển thị mã C ++ dưới dạng mã C và có lẽ chúng tôi sẽ sử dụng nhiều hơn của C ++ trong các ngôn ngữ script.

Chúng tôi cũng sẽ không phải chịu đựng những điều như COM cố gắng cung cấp giải pháp cho vấn đề này.


1
+1. Vâng tôi đồng ý. Các câu trả lời khác ở đây về cơ bản đã giải quyết vấn đề bằng cách nói rằng tiêu chuẩn hóa nhị phân sẽ cấm tối ưu hóa kiến ​​trúc cụ thể. Nhưng đó không phải là vấn đề. Không ai tranh cãi về một số định dạng thực thi nhị phân đa nền tảng. Vấn đề là không có giao diện chuẩn để tải các mô-đun C ++ một cách linh hoạt.
Charles Salvia

1

Có nhiều vấn đề về tính di động với C ++, điều này chỉ do thiếu tiêu chuẩn hóa ở cấp độ nhị phân.

Tôi không nghĩ nó khá đơn giản. Các câu trả lời được cung cấp đã cung cấp lý do tuyệt vời về việc thiếu tập trung vào tiêu chuẩn hóa, nhưng C ++ có thể quá giàu ngôn ngữ để phù hợp để cạnh tranh thực sự với C như một tiêu chuẩn ABI.

Chúng ta có thể đi vào xáo trộn tên do quá tải chức năng, không tương thích vtable, không tương thích với các ngoại lệ ném qua ranh giới mô-đun, v.v ... Tất cả những điều này là một nỗi đau thực sự, và tôi ước họ ít nhất có thể tiêu chuẩn hóa bố cục vtable.

Nhưng một tiêu chuẩn ABI không chỉ là tạo ra các dylib C ++ được sản xuất trong một trình biên dịch có khả năng được sử dụng bởi một nhị phân khác được xây dựng bởi một trình biên dịch khác. ABI được sử dụng nhiều ngôn ngữ . Sẽ thật tuyệt nếu họ ít nhất có thể bao quát phần đầu tiên, nhưng không có cách nào tôi thấy C ++ thực sự cạnh tranh với C ở cấp độ ABI phổ quát rất quan trọng để tạo ra các dylib tương thích rộng rãi nhất.

Hãy tưởng tượng một cặp hàm đơn giản được xuất như thế này:

void f(Foo foo);
void f(Bar bar, int val);

... và tưởng tượng FooBarlà các lớp với các hàm tạo được tham số hóa, các hàm tạo sao chép, các hàm tạo di chuyển và các hàm hủy không tầm thường.

Sau đó lấy kịch bản của Python / Lua / C # / Java / Haskell / etc. nhà phát triển đang cố gắng nhập mô-đun này và sử dụng nó trong ngôn ngữ của họ.

Trước tiên, chúng ta cần một tiêu chuẩn xáo trộn tên cho cách xuất các biểu tượng sử dụng quá tải hàm. Đây là một phần dễ dàng hơn. Tuy nhiên, nó không thực sự là tên "mangling". Vì người dùng của dylib phải tìm kiếm các biểu tượng theo tên, sự quá tải ở đây sẽ dẫn đến các tên không giống như một mớ hỗn độn. Có lẽ tên biểu tượng có thể giống "f_Foo" "f_Bar_int"hoặc một cái gì đó thuộc loại đó. Chúng tôi phải chắc chắn rằng họ không thể đụng độ với một tên thực sự được xác định bởi nhà phát triển, có thể bảo lưu một số ký hiệu / ký tự / quy ước cho việc sử dụng ABI.

Nhưng bây giờ một kịch bản khó khăn hơn. Làm thế nào để nhà phát triển Python, ví dụ, gọi các hàm tạo di chuyển, sao chép các hàm tạo và các hàm hủy? Có lẽ chúng ta có thể xuất khẩu chúng như một phần của dylib. Nhưng nếu FooBarđược xuất khẩu trong các mô-đun khác nhau thì sao? Chúng ta có nên nhân đôi các biểu tượng và triển khai liên quan đến dylib này hay không? Tôi đề nghị chúng ta nên làm, vì nó có thể trở nên thực sự khó chịu rất nhanh nếu không bắt đầu phải vướng vào nhiều giao diện dylib chỉ để tạo một đối tượng ở đây, chuyển nó ở đây, sao chép nó ở đây, phá hủy nó ở đây. Mặc dù mối quan tâm cơ bản tương tự có thể phần nào áp dụng trong C (chỉ bằng tay / rõ ràng hơn), C có xu hướng tránh điều này chỉ theo bản chất của cách mọi người lập trình với nó.

Đây chỉ là một mẫu nhỏ của sự vụng về. Điều gì xảy ra khi một trong các fhàm trên ném một BazException(cũng là một lớp C ++ với các hàm tạo và hàm hủy và dẫn xuất std :: ngoại lệ) vào JavaScript?

Tốt nhất tôi nghĩ rằng chúng ta chỉ có thể hy vọng tiêu chuẩn hóa một ABI hoạt động từ một nhị phân được tạo bởi một trình biên dịch C ++ sang một nhị phân khác được tạo bởi một nhị phân khác. Điều đó sẽ là tuyệt vời, tất nhiên, nhưng tôi chỉ muốn chỉ ra điều này. Thông thường đi kèm với những mối quan tâm như vậy để phân phối một thư viện tổng quát hoạt động các trình biên dịch chéo cũng thường là mong muốn làm cho nó thực sự khái quát và tương thích các ngôn ngữ chéo.

Đề xuất giải pháp

Giải pháp được đề xuất của tôi sau khi vật lộn để tìm cách sử dụng giao diện C ++ cho API / ABI trong nhiều năm với giao diện kiểu COM là trở thành nhà phát triển "C / C ++" (chơi chữ).

Sử dụng C để tạo các ABI phổ quát đó, với C ++ để thực hiện. Chúng ta vẫn có thể thực hiện những việc như xuất các hàm trả về các con trỏ tới các lớp C ++ mờ với các hàm rõ ràng để tạo và hủy các đối tượng như vậy trên heap. Cố gắng yêu thích thẩm mỹ C đó từ góc độ ABI ngay cả khi chúng tôi hoàn toàn sử dụng C ++ để thực hiện. Các giao diện trừu tượng có thể được mô hình hóa bằng cách sử dụng các bảng con trỏ hàm. Thật tẻ nhạt khi bọc thứ này vào API C, nhưng lợi ích và khả năng tương thích của bản phân phối đi kèm sẽ có xu hướng làm cho nó rất đáng giá.

Sau đó, nếu chúng tôi không thích sử dụng giao diện này trực tiếp nhiều như vậy (có lẽ ít nhất chúng tôi không nên vì lý do RAII), chúng tôi có thể gói tất cả những gì chúng tôi muốn trong thư viện C ++ được liên kết tĩnh mà chúng tôi gửi kèm với SDK. Khách hàng C ++ có thể sử dụng điều đó.

Các máy khách Python sẽ không muốn sử dụng trực tiếp giao diện C hoặc C ++ vì không có cách nào để tạo các pythonique đó. Họ sẽ muốn gói nó vào các giao diện pythonique của riêng họ, vì vậy thực sự là chúng tôi chỉ xuất một API C / ABI tối thiểu để làm điều đó dễ dàng nhất có thể.

Tôi nghĩ rằng rất nhiều ngành công nghiệp C ++ sẽ được hưởng lợi từ việc này hơn là cố gắng vận chuyển các giao diện kiểu COM một cách bướng bỉnh. Nó cũng sẽ làm cho tất cả cuộc sống của chúng ta dễ dàng hơn khi những người sử dụng các dylib này không phải lo lắng về ABI vụng về. C làm cho nó đơn giản và sự đơn giản của nó từ phối cảnh ABI cho phép chúng ta tạo ra các API / ABI hoạt động tự nhiên và tối giản cho tất cả các loại FFI.


1
"Sử dụng C để tạo các ABI phổ quát đó, với C ++ để thực hiện." ... Tôi cũng làm như vậy, giống như nhiều người khác!
Nawaz

-1

Tôi không biết tại sao nó không chuẩn hóa ở cấp độ nhị phân. Nhưng tôi biết những gì tôi làm về nó. Trên Windows tôi khai báo hàm extern "C" BOOL WINAPI. (Tất nhiên thay thế BOOL bằng bất kỳ loại chức năng nào.) Và chúng được xuất một cách sạch sẽ.


2
Nhưng nếu bạn khai báo extern "C", nó sẽ sử dụng C ABI, một tiêu chuẩn thực tế trên phần cứng PC thông thường mặc dù nó không bị áp đặt bởi bất kỳ loại ủy ban nào.
Billy ONeal

-3

Sử dụng unzip foo.zip && make foo.exe && foo.exenếu bạn muốn tính di động của nguồn của bạn.

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.