it-swarm-vi.com

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ể?

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?

25
Joshua Partogi

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 chức năng additup được xác định 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ó biểu tượng 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.

(Bằng cách "khai báo", ý tôi là cung cấp một nguyên mẫu hàm không có phần thân; bằng cách "định nghĩa" Tôi có nghĩa là cung cấp mã thực tế của phần thân hàm; đây là thuật ngữ C tiêu chuẩn.)

29
David Conrad

C và C++ hoạt động rất giống nhau về vấn đề này - bạn có thể có các hàm inline trong các tiêu đề. Trong C++, bất kỳ phương thức nào có phần thân 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.

30
Simon Richter

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 tìm thấy cùng một biểu tượng trong 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.

Các '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 được 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.

7
vrdhn

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

4
SK-logic

Dấu ngoặc kép tiêu chuẩn C++

Bản nháp tiêu chuẩn C++ 17 N4659 10.1.6 "Trình xác định nội tuyến" nói rằng các phương thức này hoàn toàn là nội tuyến:

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

và sau đó tiếp tục đi xuống, chúng ta thấy rằng các phương thức nội tuyến không chỉ có thể, mà 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 trong đó nó được sử dụng odr và sẽ có cùng định nghĩa chính xác 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ề chức năng 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 rằng biểu tượng MyClass::myMethod được đánh dấu là yếu trên các tệp đối tượng ELF, ngụ ý rằng nó có thể xuất hiện trên nhiều tệp đố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.