Nội tuyến extern làm gì?


93

Tôi hiểu rằng inlinebản thân nó là một gợi ý cho trình biên dịch và tùy theo quyết định của nó, nó có thể nội dòng hoặc không nội tuyến hàm và nó cũng sẽ tạo ra mã đối tượng có thể liên kết.

Tôi nghĩ rằng điều đó static inlinecũng tương tự (có thể có hoặc có thể không nội dòng) nhưng sẽ không tạo ra mã đối tượng có thể liên kết khi nội tuyến (vì không có mô-đun nào khác có thể liên kết với nó).

Chỗ nào extern inlinephù hợp với bức tranh?

Giả sử tôi muốn thay thế một macro tiền xử lý bằng một hàm nội tuyến và yêu cầu hàm này được nội tuyến (ví dụ: vì nó sử dụng __FILE____LINE__macro sẽ giải quyết cho người gọi chứ không phải hàm được gọi này). Đó là, tôi muốn xem lỗi trình biên dịch hoặc trình liên kết trong trường hợp hàm không được nội tuyến. Làm được extern inlineđiều này? (Tôi cho rằng, nếu không, không có cách nào để đạt được hành vi này ngoài việc gắn bó với macro.)

Có sự khác biệt giữa C ++ và C không?

Có sự khác biệt giữa các nhà cung cấp và phiên bản trình biên dịch khác nhau không?

Câu trả lời:


129

trong K&R C hoặc C89, nội tuyến không phải là một phần của ngôn ngữ. Nhiều trình biên dịch đã triển khai nó như một phần mở rộng, nhưng không có ngữ nghĩa xác định về cách nó hoạt động. GCC là một trong những người đầu tiên thực hiện nội tuyến, và giới thiệu inline, static inlineextern inlinecấu trúc; hầu hết các trình biên dịch trước C99 thường tuân theo hướng dẫn của nó.

GNU89:

  • inline: hàm có thể được nội dòng (mặc dù nó chỉ là một gợi ý). Một phiên bản ngoài dòng luôn được phát ra và có thể nhìn thấy bên ngoài. Do đó, bạn chỉ có thể xác định một nội tuyến như vậy trong một đơn vị biên dịch và mọi đơn vị khác cần xem nó như một hàm ngoài dòng (hoặc bạn sẽ nhận được các ký hiệu trùng lặp tại thời điểm liên kết).
  • extern inline sẽ không tạo phiên bản ngoài dòng, nhưng có thể gọi một phiên bản (do đó bạn phải xác định trong một số đơn vị biên dịch khác. Tuy nhiên, quy tắc một định nghĩa được áp dụng; phiên bản ngoài dòng phải có cùng mã với nội tuyến được cung cấp ở đây, trong trường hợp trình biên dịch gọi thay thế.
  • static inlinesẽ không tạo ra một phiên bản ngoại tuyến có thể nhìn thấy bên ngoài, mặc dù nó có thể tạo một tệp tĩnh. Quy tắc một định nghĩa không áp dụng, vì không bao giờ có một ký hiệu bên ngoài được phát ra cũng như không gọi đến một định nghĩa.

C99 (hoặc GNU99):

  • inline: như GNU89 "extern inline"; không có chức năng nhìn thấy bên ngoài nào được phát ra, nhưng một chức năng có thể được gọi và vì vậy phải tồn tại
  • extern inline: như GNU89 "inline": mã có thể nhìn thấy bên ngoài được phát ra, vì vậy nhiều nhất một đơn vị dịch có thể sử dụng mã này.
  • static inline: như GNU89 "static inline". Đây là chiếc di động duy nhất giữa gnu89 và c99

C ++:

Một hàm nội tuyến ở bất kỳ đâu phải nội tuyến ở mọi nơi, với cùng một định nghĩa. Trình biên dịch / trình liên kết sẽ sắp xếp nhiều trường hợp của biểu tượng. Không có định nghĩa về static inlinehoặc extern inline, mặc dù nhiều trình biên dịch có chúng (thường theo mô hình gnu89).


2
Trong Classic Classic C, 'inline' không phải là một từ khóa; nó đã có sẵn để sử dụng như một tên biến. Điều này sẽ áp dụng đối với C89 và pre-chuẩn (K & R) C.
Jonathan Leffler

Có vẻ như bạn chính xác. Đã sửa. Tôi nghĩ rằng nó đã được dành riêng như một từ khóa trong C89 (mặc dù không phải trong K & R), nhưng tôi dường như tôi misremembered
puetzk

Tôi muốn thêm điều đó cho Visual C ++ của Microsoft, có một từ khóa __forceinline sẽ thực thi chức năng của bạn được nội tuyến. Đây rõ ràng là một phần mở rộng dành riêng cho trình biên dịch chỉ dành cho VC ++.
unsitled8468927

Có sự khác biệt nào giữa C99 "extern inline" và không có thông số kỹ thuật nào không?
Jo So

Ngữ nghĩa là không; giống như một hàm không nội tuyến, extern inlinetuân theo một quy tắc định nghĩa và đây là định nghĩa. Nhưng nếu heuristics tối ưu hóa do triển khai xác định tuân theo khuyến nghị sử dụng inlinetừ khóa như một gợi ý rằng "lệnh gọi hàm càng nhanh càng tốt" (ISO 9899: 1999 §6.7.4 (5), extern inlinesố đếm
puetzk

31

Tôi tin rằng bạn hiểu sai __FILE__ và __LINE__ dựa trên tuyên bố này:

vì nó sử dụng macro __FILE__ và __LINE__ sẽ giải quyết cho người gọi chứ không phải hàm được gọi này

Có một số giai đoạn biên dịch và tiền xử lý là giai đoạn đầu tiên. __FILE__ và __LINE__ được thay thế trong giai đoạn đó. Vì vậy, vào thời điểm trình biên dịch có thể xem xét hàm cho nội tuyến, chúng đã được thay thế.


14

Có vẻ như bạn đang cố viết một cái gì đó như thế này:

inline void printLocation()
{
  cout <<"You're at " __FILE__ ", line number" __LINE__;
}

{
...
  printLocation();
...
  printLocation();
...
  printLocation();

và hy vọng rằng bạn sẽ nhận được các giá trị khác nhau được in mỗi lần. Như Don nói, bạn sẽ không, bởi vì __FILE__ và __LINE__ được thực hiện bởi bộ tiền xử lý, nhưng nội dòng được thực hiện bởi trình biên dịch. Vì vậy, dù bạn gọi printLocation từ đâu, bạn sẽ nhận được kết quả tương tự.

Cách duy nhất bạn có thể làm cho điều này hoạt động là đặt printLocation thành một macro. (Vâng tôi biết...)

#define PRINT_LOCATION  {cout <<"You're at " __FILE__ ", line number" __LINE__}

...
  PRINT_LOCATION;
...
  PRINT_LOCATION;
...

18
Một thủ thuật phổ biến là đối với macro PRINT_LOCATION để gọi một hàm printLocation, chuyển FILELINE làm tham số. Điều này có thể dẫn đến hành vi của trình gỡ lỗi / trình chỉnh sửa / vv tốt hơn khi thân hàm không phải là tầm thường.
Steve Jessop 20/10/08

@Roddy Hãy xem giải pháp của tôi - phần mở rộng của bạn nhưng toàn diện hơn và có thể mở rộng.
hăng hái

@SteveJessop Một cái gì đó giống như tôi đã liệt kê trong giải pháp bên dưới?
hăng hái

3

Tình huống với nội tuyến, nội tuyến tĩnh và nội tuyến ngoại tuyến rất phức tạp, đặc biệt là vì gcc và C99 xác định các ý nghĩa hơi khác nhau cho hành vi của chúng (và có lẽ là cả C ++). Bạn có thể tìm thấy một số thông tin hữu ích và chi tiết về những gì họ làm trong C tại đây .


2

Macro là sự lựa chọn của bạn ở đây thay vì các hàm nội tuyến. Một dịp hiếm hoi mà macro cai trị các hàm nội tuyến. Hãy thử cách sau: Tôi đã viết mã "MACRO MAGIC" này và nó sẽ hoạt động! Đã thử nghiệm trên gcc / g ++ Ubuntu 10.04

//(c) 2012 enthusiasticgeek (LOGGING example for StackOverflow)

#ifdef __cplusplus

#include <cstdio>
#include <cstring>

#else

#include <stdio.h>
#include <string.h>

#endif

//=========== MACRO MAGIC BEGINS ============

//Trim full file path
#define __SFILE__ (strrchr(__FILE__,'/') ? strrchr(__FILE__,'/')+1 : __FILE__ )

#define STRINGIFY_N(x) #x
#define TOSTRING_N(x) STRINGIFY_N(x)
#define _LINE (TOSTRING_N(__LINE__))

#define LOG(x, s...) printf("(%s:%s:%s)"  x "\n" , __SFILE__, __func__, _LINE, ## s);

//=========== MACRO MAGIC ENDS ============

int main (int argc, char** argv) {

  LOG("Greetings StackOverflow! - from enthusiasticgeek\n");

  return 0;
}

Đối với nhiều tệp, hãy xác định các macro này trong một tệp tiêu đề riêng biệt, bao gồm các macro giống nhau trong mỗi tệp c / cc / cxx / cpp. Vui lòng ưu tiên các hàm nội tuyến hoặc số nhận dạng const (tùy trường hợp yêu cầu) hơn macro nếu có thể.


2

Thay vì trả lời "nó làm được gì?", Tôi đang trả lời "làm thế nào để tôi làm cho nó làm những gì tôi muốn?" Có 5 loại nội tuyến, tất cả đều có sẵn trong GNU C89, C99 tiêu chuẩn và C ++:

luôn nội dòng, trừ khi địa chỉ được sử dụng

Thêm __attribute__((always_inline))vào bất kỳ khai báo nào, sau đó sử dụng một trong các trường hợp dưới đây để xử lý khả năng địa chỉ của nó bị lấy.

Bạn có thể không bao giờ nên sử dụng nó, trừ khi bạn cần ngữ nghĩa của nó (ví dụ: để ảnh hưởng đến assembly theo một cách nhất định hoặc để sử dụng alloca). Trình biên dịch thường biết rõ hơn bạn liệu nó có đáng hay không.

nội dòng và phát ra một biểu tượng yếu (như C ++, hay còn gọi là "chỉ làm cho nó hoạt động")

__attribute__((weak))
void foo(void);
inline void foo(void) { ... }

Lưu ý rằng điều này để lại một loạt các bản sao của cùng một mã và trình liên kết chọn một bản tùy ý.

nội dòng, nhưng không bao giờ phát ra bất kỳ ký hiệu nào (để lại các tham chiếu bên ngoài)

__attribute__((gnu_inline))
extern inline void foo(void) { ... }

phát ra luôn luôn (cho một TU, để giải quyết trước đó)

Phiên bản gợi ý phát ra một biểu tượng yếu trong C ++, nhưng một biểu tượng mạnh trong một trong hai phương ngữ của C:

void foo(void);
inline void foo(void) { ... }

Hoặc bạn có thể làm điều đó mà không cần gợi ý, phát ra một biểu tượng mạnh bằng cả hai ngôn ngữ:

void foo(void) { ... }

Nói chung, bạn biết ngôn ngữ TU của bạn là gì khi bạn cung cấp các định nghĩa và có thể không cần nội dòng nhiều.

nội tuyến và phát ra trong mỗi TU

static inline void foo(void) { ... }

Đối với tất cả những điều này, ngoại trừ staticmột, bạn có thể thêm một void foo(void)khai báo ở trên. Điều này giúp thực hiện "phương pháp hay nhất" là viết tiêu đề rõ ràng, sau đó nhập #includemột tệp riêng biệt với các định nghĩa nội dòng. Sau đó, nếu sử dụng nội tuyến kiểu C, #definemột số macro khác nhau trong một TU chuyên dụng để cung cấp các định nghĩa ngoài dòng.

Đừng quên extern "C"nếu tiêu đề có thể được sử dụng từ cả C và C ++!


Thiếu: MSVC thì sao? Nó có một số phần mở rộng phương ngữ C89, nhưng tôi không bao giờ sử dụng MSVC và không biết cách chạy phần mở rộng nmtương đương của nó .
o11c
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.