2014-10-28 27 views
6

Mã này có dẫn đến hành vi không xác định không?các hàm bên trong "C" bên ngoài

header.h

#ifdef __cplusplus 
extern "C" 
{ 
#endif 

inline int foo(int a) 
{ 
    return a * 2; 
} 

#ifdef __cplusplus 
} 
#endif 

def.c

#include "header.h" 

extern inline int foo(int a); 

use.c

#include "header.h" 

int bar(int a) 
{ 
    return foo(a + 3); 
} 

main.cpp

#include <stdio.h> 
#include "header.h" 

extern "C" 
{ 
    int bar(int a); 
} 

int main(int argc, char** argv) 
{ 
    printf("%d\n", foo(argc)); 
    printf("%d\n", bar(argc)); 
} 

Đây là một ví dụ về một chương trình nơi một chức năng inline phải được sử dụng trong cả C và C++. Nó có hoạt động nếu def.c bị xóa và foo không được sử dụng trong C? (Điều này được giả định rằng trình biên dịch C là C99.)

Mã này hoạt động khi biên soạn với:

gcc -std=c99 -pedantic -Wall -Wextra -c -o def.o def.c 
g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp 
gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c 
g++ -std=c++11 -pedantic -Wall -Wextra -o extern_C_inline def.o main.o use.o 

foo chỉ trong extern_C_inline một lần vì các phiên bản khác nhau mà các kết quả đầu ra trình biên dịch trong các tập tin đối tượng khác nhau được sáp nhập , nhưng tôi muốn biết liệu hành vi này có được chỉ định bởi tiêu chuẩn hay không. Nếu tôi xóa định dạng extern của foo và đặt thành static thì foo sẽ xuất hiện trong extern_C_inline nhiều lần bởi vì trình biên dịch sẽ xuất ra trong mỗi đơn vị biên dịch.

+0

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. –

+5

@BenVoigt: Vâng, nó có ngữ nghĩa hơi khác nhau. – Deduplicator

+0

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 đó. –

Trả lời

5

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()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.omain.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ó.

+0

Tại sao nó không đủ để trả lời _ "không có trình biên dịch C tham gia vào" _ vì điểm là _C linkage_, không _C compiler_, và thậm chí cả mã được viết trong một 'extern" C "' được biên dịch bởi trình biên dịch C++ ? – user2485710

+1

@ user2485710, bởi vì điều đó không đúng. 'def.c' và' use.c' được biên dịch bởi trình biên dịch C. –

+0

@JonathanWakely chắc chắn, nhưng trong một câu hỏi mà bắt đầu với 'extern" C "', mà một C + + chỉ xây dựng, bao nhiêu lần điều này sẽ được biên dịch với một trình biên dịch 'C' đơn giản? Tôi nghĩ rằng câu hỏi bị ảnh hưởng bởi thực tế là OP giả định rằng ngay cả khi sử dụng mã đó trong một cơ sở mã C++ nó được biên dịch bởi một trình biên dịch C. – user2485710

Các vấn đề liên quan