Cách sử dụng chính xác từ khóa extern trong C


235

Câu hỏi của tôi là về khi nào một chức năng nên được tham chiếu với externtừ khóa trong C.

Tôi không thấy khi nào nên sử dụng nó trong thực tế. Khi tôi đang viết một chương trình, tất cả các chức năng mà tôi sử dụng đều có sẵn thông qua các tệp tiêu đề tôi đã đưa vào. Vậy tại sao nó lại hữu ích để externcó quyền truy cập vào một cái gì đó không được hiển thị trong tệp tiêu đề?

Tôi có thể suy nghĩ về cách làm externviệc không chính xác, và nếu vậy xin vui lòng sửa cho tôi.

Chỉnh sửa: Bạn có nên làm externgì đó khi nó là khai báo mặc định không có từ khóa trong tệp tiêu đề?


Câu trả lời:


290

" extern" Thay đổi liên kết. Với từ khóa, hàm / biến được giả sử là có sẵn ở một nơi khác và việc giải quyết được hoãn lại cho trình liên kết.

Có một sự khác biệt giữa "extern" trên các hàm và trên các biến: trên các biến nó không tự khởi tạo biến đó, tức là không phân bổ bất kỳ bộ nhớ nào. Điều này cần phải được thực hiện ở một nơi khác. Do đó, điều quan trọng là bạn muốn nhập biến từ nơi khác. Đối với các hàm, điều này chỉ cho trình biên dịch biết rằng liên kết là extern. Vì đây là mặc định (bạn sử dụng từ khóa "tĩnh" để chỉ ra rằng một hàm không bị ràng buộc khi sử dụng liên kết ngoài), bạn không cần phải sử dụng nó một cách rõ ràng.


1
thì tại sao điều extern cùng là có trong Git: một phần mềm rất phổ biến và hiện đại kiểm tra xem nó: github.com/git/git/blob/master/strbuf.h
rsjethani

K & R không lưu ý rằng mặc định khai báo hàm là "extern", tuy nhiên câu trả lời này giải quyết được sự nhầm lẫn của tôi!
acgtyrant

@rsjethani Tôi nghĩ rằng nó là để làm cho tài liệu nghiêm ngặt hơn và định dạng.
acgtyrant

Có thể một câu hỏi ngớ ngẩn, nhưng làm thế nào để so sánh với tuyên bố chuyển tiếp?
weberc2

196

extern nói với trình biên dịch rằng dữ liệu này được xác định ở đâu đó và sẽ được kết nối với trình liên kết.

Với sự giúp đỡ của các câu trả lời ở đây và nói chuyện với một vài người bạn ở đây là ví dụ thực tế về việc sử dụng extern .

Ví dụ 1 - để hiển thị một cạm bẫy:

File stdio.h:

int errno;
/* other stuff...*/

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Nếu myCFile1.o và myCFile2.o được liên kết, mỗi tệp c có các bản sao errno riêng biệt . Đây là một vấn đề vì cùng một lỗi được cho là có sẵn trong tất cả các tệp được liên kết.

Ví dụ 2 - Bản sửa lỗi.

File stdio.h:

extern int errno;
/* other stuff...*/

File stdio.c

int errno;

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Bây giờ nếu cả myCFile1.o và MyCFile2.o được liên kết bởi trình liên kết thì cả hai sẽ trỏ đến cùng một errno . Do đó, giải quyết việc thực hiện với extern .


70
Vấn đề không phải là các mô đun myCFile1 và myCFile2 có một bản sao riêng của errno, đó là cả hai đều phơi bày một biểu tượng gọi là "errno". Khi trình liên kết nhìn thấy điều này, nó không biết nên chọn "errno" nào, vì vậy nó sẽ bảo lãnh với một thông báo lỗi.
cwick

2
"Liên kết bởi trình liên kết" thực sự có nghĩa là gì? mọi người sử dụng thuật ngữ này, tôi không tìm thấy bất kỳ định nghĩa nào :(
Marcel Falliere

7
@MarcelFalliere Wiki ~ Trình biên dịch tự biên dịch từng tệp nguồn và tạo một tệp đối tượng cho mỗi tệp nguồn. Linker liên kết các tệp đối tượng này thành 1 tệp thực thi.
Bitterblue

1
@cwick gcc không đưa ra lỗi hoặc cảnh báo ngay cả sau khi sử dụng -Wall-pedantic. Tại sao ? và làm thế nào ?
b-ak

6
Không một người bảo vệ bao gồm bảo vệ chống lại điều chính xác này?
obskyr

32

Nó đã được tuyên bố rằng externtừ khóa là dư thừa cho các chức năng.

Đối với các biến được chia sẻ trên các đơn vị biên dịch, bạn nên khai báo chúng trong tệp tiêu đề với từ khóa extern, sau đó xác định chúng trong một tệp nguồn duy nhất, không có từ khóa extern. Tệp nguồn duy nhất phải là tệp chia sẻ tên tệp tiêu đề, để thực hành tốt nhất.


@aib "dự phòng cho các chức năng", hãy kiểm tra nhận xét của tôi trong câu trả lời của bluebrother.
rsjethani

Điều gì xảy ra nếu bạn không muốn để lộ bất kỳ chức năng nào trong tệp tiêu đề? Sẽ không tốt hơn nếu khai báo biến trong một tệp C và truy cập nó bằng extern trong một tệp khác; hãy để trình liên kết giải quyết vấn đề và ẩn phần còn lại của tiêu đề.
ste3e

16

Nhiều năm sau, tôi phát hiện ra câu hỏi này. Sau khi đọc mọi câu trả lời và bình luận, tôi nghĩ rằng tôi có thể làm rõ một vài chi tiết ... Điều này có thể hữu ích cho những người đến đây thông qua tìm kiếm của Google.

Câu hỏi cụ thể là về việc sử dụng các hàm "extern", vì vậy tôi sẽ bỏ qua việc sử dụng "extern" với các biến toàn cục.

Hãy xác định 3 nguyên mẫu hàm:

//--------------------------------------
//Filename: "my_project.H"
extern int function_1(void);
static int function_2(void);
       int function_3(void);

Tệp tiêu đề có thể được sử dụng bởi mã nguồn chính như sau:

//--------------------------------------
//Filename: "my_project.C"
#include "my_project.H"

void main(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 1234;

Để biên dịch và liên kết, chúng ta phải định nghĩa "function_2" trong cùng một tệp mã nguồn nơi chúng ta gọi hàm đó. Hai hàm khác có thể được định nghĩa trong mã nguồn khác nhau " .C" hoặc chúng có thể nằm trong bất kỳ tệp nhị phân nào ( .OBJ, * .LIB, * .DLL) mà chúng tôi không có mã nguồn.

Hãy thêm lại tiêu đề "my_project.H" vào một tệp "* .C" khác để hiểu rõ hơn về sự khác biệt. Trong cùng một dự án, chúng tôi thêm tệp sau:

//--------------------------------------
//Filename: "my_big_project_splitted.C"
#include "my_project.H"

void old_main_test(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 5678;

int function_1(void) return 12;
int function_3(void) return 34;

Các tính năng quan trọng cần chú ý:

  • Khi một hàm được định nghĩa là "tĩnh" trong tệp tiêu đề, trình biên dịch / liên kết phải tìm một phiên bản của hàm có tên đó trong mỗi mô-đun sử dụng tệp bao gồm tệp đó.

  • Một chức năng là một phần của thư viện C chỉ có thể được thay thế trong một mô-đun bằng cách xác định lại một nguyên mẫu chỉ có "tĩnh" trong mô-đun đó. Ví dụ: thay thế bất kỳ cuộc gọi nào đến "malloc" và "miễn phí" để thêm tính năng phát hiện rò rỉ bộ nhớ.

  • Trình xác định "extern" không thực sự cần thiết cho các chức năng. Khi không tìm thấy "tĩnh", một hàm luôn được coi là "extern".

  • Tuy nhiên, "extern" không phải là mặc định cho các biến. Thông thường, bất kỳ tệp tiêu đề nào xác định các biến sẽ hiển thị trên nhiều mô-đun cần sử dụng "extern". Ngoại lệ duy nhất sẽ là nếu một tệp tiêu đề được đảm bảo được bao gồm từ một và chỉ một mô-đun.

    Nhiều người quản lý dự án sau đó sẽ yêu cầu biến đó được đặt ở đầu mô-đun, không nằm trong bất kỳ tệp tiêu đề nào. Một số dự án lớn, chẳng hạn như trình giả lập trò chơi video "Mame" thậm chí yêu cầu biến đó chỉ xuất hiện phía trên hàm đầu tiên sử dụng chúng.


Vậy tại sao chính xác một hàm tĩnh cần một định nghĩa so với các hàm ngoài? (Tôi biết điều này trễ 2 năm, nhưng điều này thực sự hữu ích để hiểu)
SubLock69

2
Định nghĩa là cần thiết nếu bạn gọi hàm ở dòng 100 và kích hoạt nó ở dòng 500. Dòng 100 sẽ khai báo nguyên mẫu không xác định. Vì vậy, bạn thêm nguyên mẫu gần đầu.
Christian Gingras

15

Trong C, 'extern' được ngụ ý cho các nguyên mẫu hàm, vì một nguyên mẫu khai báo một hàm được định nghĩa ở một nơi khác. Nói cách khác, một nguyên mẫu hàm có liên kết bên ngoài theo mặc định; sử dụng 'extern' là tốt, nhưng là dư thừa.

(Nếu liên kết tĩnh là bắt buộc, hàm phải được khai báo là 'tĩnh' cả trong nguyên mẫu và tiêu đề hàm của nó, và cả hai thường sẽ nằm trong cùng một tệp .c).


8

Một bài viết rất hay mà tôi đã nói về externtừ khóa, cùng với các ví dụ: http://www.geekforgeek.org/under Hiểu-extern- keyword-in-c /

Mặc dù tôi không đồng ý rằng việc sử dụng externtrong khai báo hàm là dư thừa. Đây được coi là một thiết lập trình biên dịch. Vì vậy, tôi khuyên bạn nên sử dụng các externkhai báo hàm khi cần thiết.


3
Tôi đã đọc bài viết của geekforgeek.org trước khi tôi đến đây, nhưng thấy nó được viết khá kém. Ngoài những thiếu sót về ngữ pháp và cú pháp, nó sử dụng rất nhiều từ để tạo ra cùng một điểm nhiều lần và sau đó lướt qua thông tin quan trọng. Ví dụ, trong ví dụ 4, đột nhiên bao gồm 'somefile.h', nhưng không có gì được nói về nó ngoài: "Giả sử rằng somefile.h có định nghĩa về var". Chà, thông tin mà chúng tôi "giả sử" chỉ là thông tin tôi đang tìm kiếm. Thật không may, không có câu trả lời trên trang này là tốt hơn nhiều.
Elise van Looij

6

Nếu mỗi tệp trong chương trình của bạn lần đầu tiên được biên dịch thành tệp đối tượng, thì các tệp đối tượng được liên kết với nhau, bạn cần extern. Nó nói với trình biên dịch "Hàm này tồn tại, nhưng mã cho nó ở một nơi khác. Đừng hoảng sợ."


Ừm, đó là cách dịch thường được thực hiện: các tệp nguồn biên dịch thành các tệp đối tượng và sau đó được liên kết. Khi nào bạn không cần extern trong trường hợp đó? Bạn cũng sẽ không sử dụng #include để lấy các hàm, mà là các nguyên mẫu hàm. Tôi không hiểu bạn đang nói về cái gì.
David Thornley

Tôi dường như đang gặp vấn đề này gần đây về việc đọc sai. Xin lỗi vì điều đó. Khi tôi chưa quen với C, tôi sẽ #include "file.c" để chỉ bao gồm các hàm trong một tệp trực tiếp vào tệp khác. Sau đó, tôi tìm ra cách sử dụng 'extern'. Tôi nghĩ rằng anh ấy đã phạm sai lầm tương tự như tôi.
Chris Lutz

4

Tất cả các khai báo của hàm và biến trong tệp tiêu đề phải là extern.

Các ngoại lệ cho quy tắc này là các hàm nội tuyến được xác định trong tiêu đề và các biến - mặc dù được xác định trong tiêu đề - sẽ phải cục bộ cho đơn vị dịch (tệp nguồn mà tiêu đề được đưa vào): chúng nên được đặt static.

Trong tệp nguồn, externkhông nên được sử dụng cho các hàm và biến được xác định trong tệp. Chỉ cần tiền tố định nghĩa cục bộ với staticvà không làm gì cho các định nghĩa được chia sẻ - chúng sẽ là các ký hiệu bên ngoài theo mặc định.

Lý do duy nhất để sử dụng externtất cả trong một tệp nguồn là khai báo các hàm và biến được định nghĩa trong các tệp nguồn khác và không có tệp tiêu đề nào được cung cấp.


Nguyên mẫu hàm khai báo externlà thực sự không cần thiết. Một số người không thích nó bởi vì nó sẽ chỉ lãng phí không gian và khai báo hàm đã có xu hướng vượt quá giới hạn dòng. Những người khác thích nó bởi vì cách này, các hàm và biến có thể được xử lý theo cùng một cách.


Bạn có thể đưa ra lý do tại sao "Tất cả các khai báo hàm và biến trong tệp tiêu đề phải ở bên ngoài."? Theo tôi, các phản hồi khác cho rằng chúng ở bên ngoài theo mặc định.
lillq

@Lane: externlà tùy chọn cho khai báo hàm, nhưng tôi thích xử lý các biến và hàm theo cùng một cách - ít nhất đó là điều hợp lý nhất tôi có thể nghĩ ra, vì tôi không nhớ chính xác tại sao tôi bắt đầu làm việc này;)
Christoph

Không phải là một ý tưởng tốt hơn để luôn luôn bao gồm các biến toàn cục vào tệp C để chúng không bị các tệp C ngẫu nhiên khác bao gồm tiêu đề. Và để luôn luôn sử dụng extern trên mọi toàn cầu ngoại trừ bồn rửa thực sự được khởi tạo như một vấn đề rõ ràng; nếu nó có tiền tố extern thì nó được định nghĩa ở nơi khác.
ste3e

3

Các hàm thực sự được xác định trong các tệp nguồn khác chỉ nên được khai báo trong các tiêu đề. Trong trường hợp này, bạn nên sử dụng extern khi khai báo nguyên mẫu trong tiêu đề.

Hầu hết thời gian, các chức năng của bạn sẽ là một trong những điều sau đây (giống như một cách thực hành tốt nhất):

  • tĩnh (các hàm bình thường không hiển thị bên ngoài tệp .c đó)
  • nội tuyến tĩnh (nội tuyến từ tệp .c hoặc .h)
  • extern (khai báo trong các tiêu đề của loại tiếp theo (xem bên dưới))
  • [không có từ khóa nào] (các hàm thông thường có nghĩa là được truy cập bằng cách sử dụng khai báo bên ngoài)

Tại sao bạn sẽ xuất hiện khi khai báo nguyên mẫu nếu đây là mặc định?
lillq

@Lane: Có thể hơi thiên vị, nhưng mọi dự án lành mạnh mà tôi đã làm việc đều sử dụng quy ước sau: trong các tiêu đề, chỉ khai báo các nguyên mẫu cho các chức năng bên ngoài (do đó là bên ngoài). Trong các tệp .c, các nguyên mẫu đơn giản có thể được sử dụng để làm giảm nhu cầu đặt hàng cụ thể, nhưng chúng không nên được đặt trong các tiêu đề.
Eduard - Gabriel Munteanu

1

Khi bạn có chức năng đó được xác định trên một dll hoặc lib khác nhau, để trình biên dịch trì hoãn đến trình liên kết để tìm nó. Trường hợp điển hình là khi bạn đang gọi các chức năng từ API hệ điều hành.

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.