Chương trình hợp lệ như được viết, nhưng def.c
là bắt buộc để đảm bảo mã luôn hoạt động với tất cả trình biên dịch và bất kỳ kết hợp mức tối ưu nào cho các tệp khác nhau.
Bởi vì có một tuyên bố với extern
vào nó, def.c
cung cấp một định nghĩa bên ngoài của hàm foo()
, mà bạn có thể khẳng định với nm
:
$ nm def.o
0000000000000000 T foo
định nghĩa đó sẽ luôn có mặt trong def.o
dù rằng tệp được biên dịch.
Trong use.c
có một định nghĩa inline của foo()
, nhưng theo 6.7.4 trong tiêu chuẩn C nó là không xác định liệu các cuộc gọi đến foo()
sử dụng mà định nghĩa inline hoặc sử dụng một định nghĩa bên ngoài (trong thực tế dù nó sử dụng định nghĩa nội tuyến phụ thuộc vào việc tệp được tối ưu hóa hay không). Nếu trình biên dịch chọn sử dụng định nghĩa nội tuyến thì nó sẽ hoạt động. Nếu nó chọn không sử dụng định nghĩa nội tuyến (ví dụ: vì nó được biên dịch mà không cần tối ưu hóa) thì bạn cần một định nghĩa bên ngoài trong một số tệp khác.
Nếu không tối ưu hóa use.o
có một tài liệu tham khảo không xác định:
$ gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c
$ nm use.o
0000000000000000 T bar
U foo
Nhưng với tối ưu hóa nó không:
$ gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c -O3
$ nm use.o
0000000000000000 T bar
Trong main.cpp
sẽ có một định nghĩa về foo()
nhưng nó thường sẽ tạo ra một yếu biểu tượng, vì vậy nó có thể không được giữ bởi trình liên kết nếu định nghĩa khác được tìm thấy trong một đối tượng khác. Nếu biểu tượng yếu tồn tại, nó có thể đáp ứng bất kỳ tham chiếu có thể nào trong use.o
yêu cầu định nghĩa bên ngoài, nhưng nếu trình biên dịch inline foo()
trong main.o
thì nó có thể không phát ra bất kỳ định nghĩa nào của foo()
trong main.o
và do đó định nghĩa trong def.o
vẫn cần thiết để đáp ứng use.o
Nếu không tối ưu hóa main.o
chứa một biểu tượng yếu:
$ g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp
$ nm main.o
U bar
0000000000000000 W foo
0000000000000000 T main
U printf
Tuy nhiên biên soạn main.cpp
với -O3
inlines cuộc gọi đến foo
và thứ e biên dịch không phát ra bất kỳ biểu tượng cho nó:
$ g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp -O3
$ nm main.o
U bar
0000000000000000 T main
U printf
Vì vậy, nếu foo()
là không inlined trong use.o
nhưng được inlined trong main.o
thì bạn cần định nghĩa bên ngoài trong def.o
có nó làm việc nếu def.c đã được gỡ bỏ và foo đã không được sử dụng trong C?
Có. Nếu foo
chỉ được sử dụng trong tệp C++ thì bạn không cần định nghĩa bên ngoài của foo
trong def.o
vì main.o
có chứa định nghĩa (yếu) riêng của nó hoặc sẽ nằm trong hàm. Định nghĩa trong foo.o
chỉ cần thiết để đáp ứng các cuộc gọi không được nội tuyến đến foo
từ mã C khác.
Bên cạnh: cáC++ biên dịch C được phép bỏ qua tạo ra bất kỳ biểu tượng cho foo
khi tối ưu hóa main.o
vì chuẩn C++ nói rằng một chức năng tuyên bố inline
trong một đơn vị dịch phải được khai báo nội tuyến trong tất cả đơn vị dịch thuật và để gọi hàm được khai báo inline
, định nghĩa phải có sẵn trong cùng một tệp với cuộc gọi. Điều đó có nghĩa là trình biên dịch biết rằng nếu một số tệp khác muốn gọi foo()
thì tệp khác phải chứa định nghĩa foo()
và do đó khi tệp khác được biên dịch trình biên dịch sẽ có thể tạo ra định nghĩa ký hiệu yếu khác của hàm (hoặc nội tuyến nó) khi cần thiết. Vì vậy, không cần phải xuất ra foo
trong main.o
nếu tất cả các cuộc gọi trong main.o
đã được nội tuyến.
Đây là những ngữ nghĩa differnet từ C, nơi mà các định nghĩa nội tuyến trong use.c
có thể được bỏ qua bởi trình biên dịch, và định nghĩa bên ngoài trong def.o
phải tồn tại ngay cả khi không có gì trong def.c
gọi nó.
C có nhận ra từ khóa 'nội tuyến 'không? Tôi nghĩ họ đánh vần nó theo cách khác. –
@BenVoigt: Vâng, nó có ngữ nghĩa hơi khác nhau. – Deduplicator
Tôi không biết tại sao tôi nghĩ phiên bản C có một số dấu gạch dưới trong đó. –