Tại sao bạn có thể có định nghĩa phương thức bên trong tệp tiêu đề trong C ++ khi trong C bạn không thể?


23

Trong C, bạn không thể có định nghĩa / thực hiện chức năng bên trong tệp tiêu đề. Tuy nhiên, trong C ++, bạn có thể thực hiện phương thức đầy đủ bên trong tệp tiêu đề. Tại sao hành vi khác nhau?

Câu trả lời:


28

Trong C, nếu bạn xác định một hàm trong tệp tiêu đề, thì hàm đó sẽ xuất hiện trong mỗi mô-đun được biên dịch bao gồm tệp tiêu đề đó và một biểu tượng công khai sẽ được xuất cho hàm. Vì vậy, nếu hàm additup được định nghĩa trong header.h và foo.c và bar.c đều bao gồm header.h, thì foo.o và bar.o đều sẽ bao gồm các bản sao của additup.

Khi bạn đi liên kết hai tệp đối tượng đó với nhau, trình liên kết sẽ thấy rằng phần bổ trợ biểu tượng được xác định nhiều lần và sẽ không cho phép.

Nếu bạn khai báo hàm là tĩnh, thì sẽ không có ký hiệu nào được xuất. Các tệp đối tượng foo.o và bar.o vẫn sẽ chứa các bản sao mã riêng biệt cho hàm và họ sẽ có thể sử dụng chúng, nhưng trình liên kết sẽ không thể thấy bất kỳ bản sao nào của hàm, vì vậy nó sẽ không phàn nàn. Tất nhiên, không có mô-đun nào khác có thể nhìn thấy chức năng. Và chương trình của bạn sẽ được mở rộng với hai bản sao giống nhau của cùng một chức năng.

Nếu bạn chỉ khai báo hàm trong tệp tiêu đề, nhưng không xác định nó và sau đó xác định nó chỉ trong một mô-đun, thì trình liên kết sẽ thấy một bản sao của hàm và mọi mô-đun trong chương trình của bạn sẽ có thể nhìn thấy nó và sử dụng nó. Và chương trình biên dịch của bạn sẽ chỉ chứa một bản sao của hàm.

Vì vậy, bạn có thể có định nghĩa hàm trong tệp tiêu đề trong C, đó chỉ là kiểu xấu, dạng xấu và ý tưởng xấu xung quanh.

.


2
Đó không phải là một ý tưởng tồi - loại điều này có thể được tìm thấy ngay cả trong các tiêu đề GNU Libc.
SK-logic

nhưng những gì về gói thành ngữ của tệp tiêu đề trong một chỉ thị biên dịch có điều kiện? Sau đó, ngay cả với hàm được khai báo AND được xác định trong tiêu đề, nó sẽ chỉ được tải một lần. Tôi mới sử dụng C, vì vậy tôi có thể hiểu lầm.
dùng305964

2
@papiro Vấn đề là, gói đó chỉ bảo vệ trong một lần chạy trình biên dịch. Vì vậy, nếu foo.c được biên dịch thành foo.o trong một lần chạy, bar.c thành bar.o trong một lần khác, và foo.o và bar.o được liên kết thành a.out trong một phần ba (như thông thường), gói đó không ngăn chặn nhiều phiên bản của nó, một trong mỗi tệp đối tượng.
David Conrad

Không phải là vấn đề được mô tả ở đây những gì #ifndef HEADER_Hđược cho là để ngăn chặn?
Robert Harvey

27

C và C ++ hoạt động rất giống nhau về vấn đề này - bạn có thể có các inlinechức năng trong các tiêu đề. Trong C ++, bất kỳ phương thức nào có cơ thể nằm trong định nghĩa lớp đều được ngầm định inline. Nếu bạn muốn làm tương tự trong C, hãy khai báo các hàm static inline.


" Khai báo các hàmstatic inline " ... và bạn vẫn sẽ có nhiều bản sao của hàm trong mỗi đơn vị dịch sử dụng nó. Trong C ++ không có static inlinechức năng, bạn sẽ chỉ có một bản sao. Để thực sự có triển khai trong tiêu đề trong C, bạn phải 1) đánh dấu việc thực hiện là inline(ví dụ inline void func(){do_something();}) và 2) thực sự nói rằng hàm này sẽ nằm trong một số đơn vị dịch cụ thể (ví dụ void func();).
Ruslan

6

Khái niệm về một tệp tiêu đề cần một lời giải thích nhỏ:

Hoặc bạn cung cấp một tệp trên dòng lệnh của trình biên dịch hoặc thực hiện '#include'. Hầu hết các trình biên dịch chấp nhận một tệp lệnh với phần mở rộng c, C, cpp, c ++, v.v. làm tệp nguồn. Tuy nhiên, chúng thường bao gồm một tùy chọn dòng lệnh để cho phép sử dụng bất kỳ phần mở rộng tùy ý nào cho một tệp nguồn.

Nói chung, tệp được cung cấp trên dòng lệnh được gọi là 'Nguồn' và tệp được bao gồm được gọi là 'Tiêu đề'.

Bước tiền xử lý thực sự lấy tất cả chúng và làm cho mọi thứ xuất hiện như một tệp lớn cho trình biên dịch. Những gì trong tiêu đề hoặc trong nguồn thực sự không liên quan tại thời điểm này. Thường có một tùy chọn của trình biên dịch có thể hiển thị đầu ra của giai đoạn này.

Vì vậy, đối với mỗi tệp được đưa ra trên dòng lệnh của trình biên dịch, một tệp lớn được cung cấp cho trình biên dịch. Điều này có thể có mã / dữ liệu sẽ chiếm bộ nhớ và / hoặc tạo một biểu tượng được tham chiếu từ các tệp khác. Bây giờ mỗi cái sẽ tạo ra một hình ảnh 'đối tượng'. Trình liên kết có thể đưa ra một 'biểu tượng trùng lặp' nếu cùng một biểu tượng được tìm thấy trong nhiều hơn hai tệp đối tượng đang được liên kết với nhau. Có lẽ đây là lý do; Không nên đặt mã trong tệp tiêu đề, có thể tạo các ký hiệu trong tệp đối tượng.

'Nội tuyến' thường được nội tuyến .. nhưng khi gỡ lỗi, chúng có thể không được nội tuyến. Vậy tại sao trình liên kết không đưa ra nhiều lỗi xác định? Đơn giản ... Đây là những biểu tượng 'yếu' và miễn là tất cả dữ liệu / mã cho một biểu tượng yếu từ tất cả các đối tượng có cùng kích thước và nội dung, liên kết sẽ giữ một bản sao và thả bản sao từ các đối tượng khác. Nó hoạt động.


3

Bạn có thể làm điều này trong C99: các inlinechức năng được đảm bảo được cung cấp ở một nơi khác, vì vậy nếu một chức năng không được nội tuyến, định nghĩa của nó được dịch thành một tuyên bố (nghĩa là việc thực hiện bị loại bỏ). Và, tất nhiên, bạn có thể sử dụng static.


1

Báo giá chuẩn C ++

Các C ++ 17 N4659 tiêu chuẩn dự thảo 10.1.6 "The inline specifier" nói phương pháp mà đang ngầm inline:

4 Hàm được định nghĩa trong định nghĩa lớp là hàm nội tuyến.

và sau đó đi sâu hơn, chúng ta thấy rằng các phương thức nội tuyến không chỉ có thể, mà còn phải được xác định trên tất cả các đơn vị dịch thuật:

6 Hàm hoặc biến nội tuyến phải được xác định trong mọi đơn vị dịch thuật được sử dụng odr và phải có định nghĩa chính xác giống nhau trong mọi trường hợp (6.2).

Điều này cũng được đề cập rõ ràng trong một ghi chú tại 12.2.1 "Các chức năng thành viên":

1 Hàm thành viên có thể được định nghĩa (11.4) trong định nghĩa lớp của nó, trong trường hợp đó là hàm thành viên nội tuyến (10.1.6) [...]

3 [Lưu ý: Có thể có nhiều nhất một định nghĩa về hàm thành viên không nội tuyến trong một chương trình. Có thể có nhiều hơn một định nghĩa hàm thành viên nội tuyến trong một chương trình. Xem 6.2 và 10.1.6. - lưu ý cuối]

Triển khai GCC 8.3

main.cpp

struct MyClass {
    void myMethod() {}
};

int main() {
    MyClass().myMethod();
}

Biên dịch và xem các biểu tượng:

g++ -c main.cpp
nm -C main.o

đầu ra:

                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
                 U __stack_chk_fail
0000000000000000 T main

sau đó chúng ta thấy từ man nmđó các MyClass::myMethodbiểu tượng được đánh dấu là yếu trên các tập tin đối tượng ELF, mà ngụ ý rằng nó có thể xuất hiện trên nhiều file đối tượng:

"W" "w" Biểu tượng là một biểu tượng yếu chưa được gắn thẻ cụ thể dưới dạng biểu tượng đối tượng yếu. Khi một biểu tượng được xác định yếu được liên kết với một biểu tượng được xác định bình thường, biểu tượng được xác định bình thường được sử dụng không có lỗi. Khi một biểu tượng không xác định yếu được liên kết và biểu tượng không được xác định, giá trị của biểu tượng được xác định theo cách cụ thể của hệ thống mà không có lỗi. Trên một số hệ thống, chữ hoa chỉ ra rằng một giá trị mặc định đã được chỉ định.


-4

Có lẽ vì lý do tương tự mà bạn phải đặt triển khai phương thức đầy đủ bên trong định nghĩa lớp trong Java.

Chúng có thể trông giống nhau, với dấu ngoặc vuông và nhiều từ khóa giống nhau, nhưng chúng là các ngôn ngữ khác nhau.


1
Không, thực sự có một câu trả lời thực sự và một trong những mục tiêu chính của C ++ là tương thích ngược với C.
Ed S.

4
Không, nó được thiết kế để có "Mức độ tương thích C cao" và "Không tương thích vô cớ với C". (cả hai từ Stroustrup). Tôi đồng ý rằng một câu trả lời sâu hơn có thể được đưa ra, để làm nổi bật lý do tại sao sự không tương thích cụ thể này không phải là vô cớ. Hãy cung cấp một.
Paul Butcher

Tôi sẽ có, nhưng Simon Richter đã có khi tôi đăng nó. Chúng ta có thể ngụy biện về sự khác biệt giữa "tương thích ngược" và "Mức độ tương thích C cao", nhưng thực tế vẫn là câu trả lời này không chính xác. Câu lệnh cuối cùng sẽ đúng nếu chúng ta so sánh C # và C ++, nhưng không quá nhiều với C và C ++.
Ed S.
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.