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?
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:
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.
.
#ifndef HEADER_H
được cho là để ngăn chặn?
C và C ++ hoạt động rất giống nhau về vấn đề này - bạn có thể có các inline
chứ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
.
static 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
inline
chứ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();
).
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.
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::myMethod
biể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.
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.