2009-02-26 70 views
22

Sự khác nhau giữa hàm thành viên tĩnh và hàm liên kết "C" bên ngoài là gì? Ví dụ, khi sử dụng "makecontext" trong C++, tôi cần chuyển một con trỏ tới hàm. Google khuyên bạn nên sử dụng liên kết "C" bên ngoài cho nó, bởi vì "makecontext" là C. Nhưng tôi phát hiện ra rằng sử dụng các công trình tĩnh là tốt. Tôi chỉ may mắn hay ...static vs extern "C"/"C++"

class X { 
    public: 
    static void proxy(int i) {} 
} 
makecontext(..., (void (*)(void)) X::proxy, ...); 

vs

extern "C" void proxy(int i) {} 
makecontext(..., (void (*)(void)) proxy, ...); 

EDIT: Bạn có thể hiển thị một trình biên dịch hoặc kiến ​​trúc nơi phiên bản thành viên tĩnh không hoạt động (và nó không phải là một lỗi trong trình biên dịch) ?

+0

'Tôi xin lỗi, nhưng tôi vẫn chưa thuyết phục' ... về cái gì? thực tế là tiêu chuẩn là độc đoán hơn so với hành vi thực hiện ngẫu nhiên được xác định của một số trình biên dịch? –

+0

Đây là một bài viết cũ (8 năm trước ngày hôm nay) Quan điểm của tôi là vào thời điểm nào đó giống như IMHO nếu mọi cài đặt hiện tại khác với tiêu chuẩn thì có lẽ bạn nên đặt câu hỏi liệu đó có phải là tiêu chuẩn sai hay không. Tôi đang tìm kiếm các ví dụ về các nền tảng mà nó không hoạt động. –

+0

Đủ công bằng.Đó là tất nhiên không phải không nghe cho C hoặc C + + tiêu chuẩn để chứa các hậu quả ngoài ý muốn mà tất cả các trình biên dịch hiện có bỏ qua. Đây là yêu thích của tôi tại thời điểm này: http://stackoverflow.com/a/42335543/2757035 Nhưng trong trường hợp này, tôi nghĩ rằng Tiêu chuẩn khá rõ ràng nói ý nghĩa của nó là gì và triển khai có thể thay đổi hành vi của họ bất kỳ lúc nào nếu có một số lợi thế. –

Trả lời

33

Có, bạn chỉ là may mắn :) Các bên ngoài "C" là một liên kết ngôn ngữ cho ngôn ngữ C mà mọi trình biên dịch C++ phải hỗ trợ, bên cạnh extern "C++" là mặc định. Trình biên dịch có thể hỗ trợ các liên kết ngôn ngữ khác. GCC ví dụ hỗ trợ extern "Java" cho phép giao tiếp với mã java (mặc dù đó là khá cồng kềnh).

extern "C" cho trình biên dịch biết rằng hàm của bạn có thể gọi bằng mã C. Điều đó có thể, nhưng không phải, bao gồm quy ước gọi thích hợp và tên ngôn ngữ C thích hợp mangling (đôi khi được gọi là "trang trí") trong số những thứ khác tùy thuộc vào việc thực hiện. Nếu bạn có một hàm thành viên tĩnh, quy ước gọi cho nó là trình biên dịch C++ của bạn. Thường thì chúng giống với trình biên dịch C của nền tảng đó - vì vậy tôi đã nói bạn chỉ là may mắn. Nếu bạn có một API C và bạn vượt qua một con trỏ hàm, tốt hơn luôn đặt một đến một chức năng khai báo với extern "C" như

extern "C" void foo() { ... } 

Mặc dù kiểu con trỏ hàm không chứa các đặc điểm kỹ thuật liên kết mà trông giống như

void(*)(void) 

Mối liên kết là một phần không thể thiếu của các loại - bạn chỉ không thể diễn tả nó trực tiếp mà không cần một typedef:

extern "C" typedef void(*extern_c_funptr_t)(); 

các Comeau C++, trong chế độ nghiêm ngặt , sẽ phát ra một lỗi ví dụ nếu bạn cố gắng gán địa chỉ của hàm "C" bên ngoài ở trên thành (void(*)()), beause đây là một con trỏ trỏ đến một hàm có liên kết C++.

+1

Để thêm vào câu trả lời của litb, bạn nên đọc về các quy ước gọi điện tại Wikipedia - http://en.wikipedia.org/wiki/X86_calling_conventions. extern C ngụ ý quy ước gọi cdecl; trình biên dịch của bạn sử dụng cùng một hàm cho các hàm thành viên tĩnh. Các trình biên dịch khác cũng có thể chọn bất kỳ trình biên dịch nào khác. –

+0

@Comeau trình biên dịch, nó là một lỗi hoặc cảnh báo rằng nó phát ra? –

+0

Helltone, hãy thử http://www.comeaucomputing.com/tryitout/ nó nói: '' ComeauTest.c ", dòng 4: lỗi: không thể sử dụng giá trị của loại" void (*)() C " khởi tạo một thực thể kiểu "void (*)()" ' –

4

extern "C" vô hiệu hóa trình biên dịch tên của trình biên dịch C++ (yêu cầu cho quá tải).

Nếu bạn khai báo hàm trong A.cpp là static, thì nó không thể được tìm thấy bởi B.cpp (nó còn sót lại từ C và nó có tác dụng tương tự khi đặt hàm bên trong một không gian tên ẩn danh).

+0

Điều này không trả lời câu hỏi của tôi –

+0

Nó cũng có thể thay đổi quy ước gọi điện. –

+0

Lưu ý: OP đang nói về _methods_ tĩnh ("hàm thành viên"), không phải hàm _global_ với liên kết tĩnh. –

5

Lưu ý rằng extern Cđược đề xuất cách tương tác C/C++. Here là người chủ nói về nó. Để thêm vào câu trả lời của eduffy: lưu ý rằng các hàm tĩnh và các biến trong không gian tên chung sẽ không còn được dùng nữa. Sử dụng một không gian tên vô danh ít nhất.

Quay lại extern C: nếu bạn không sử dụng extern C, bạn sẽ phải biết chính xác tên bị xé và sử dụng nó. Đó là nhiều hơn một nỗi đau.

+0

Lưu ý: OP là nói về static _methods_ ("hàm thành viên"), không phải hàm _global_ với liên kết tĩnh. –

2

Hầu hết những gì extern "C" thực sự phụ thuộc phần lớn vào trình biên dịch. Nhiều nền tảng thay đổi tên mangling và gọi quy ước dựa trên tuyên bố, nhưng không ai trong số đó được xác định theo tiêu chuẩn. Thực sự điều duy nhất mà tiêu chuẩn yêu cầu là mã trong khối có thể gọi được từ các hàm C.Đối với câu hỏi cụ thể của bạn, tiêu chuẩn nói:

Two function types with different language linkages are distinct types even if they are otherwise identical.

Điều này có nghĩa extern "C" void proxy(int i) {}/*extern "C++"*/void proxy(int i) {} có các loại khác nhau, và kết quả là các con trỏ tới các chức năng này sẽ có các loại khác nhau là tốt. Trình biên dịch không thất bại mã của bạn với cùng lý do nó sẽ không thất bại một mảnh rất nhiều công việc như:

int *foo = (int*)50; 
makecontext(..., (void (*)(void)) foo, ...); 

Mã này có thể làm việc trên một số nền tảng, nhưng điều đó không có nghĩa là nó sẽ làm việc trên một nền tảng (ngay cả khi trình biên dịch tuân thủ đầy đủ tiêu chuẩn). Bạn đang tận dụng lợi thế của nền tảng cụ thể của bạn hoạt động như thế nào, điều này có thể ổn nếu bạn không quan tâm đến việc viết mã di động.

Đối với các hàm thành viên tĩnh, chúng không bắt buộc phải có con trỏ this để trình biên dịch miễn phí coi chúng là hàm không phải thành viên. Một lần nữa, hành vi ở đây là nền tảng cụ thể.

+0

Câu trả lời hay, nhưng bạn hoàn toàn bỏ lỡ câu hỏi của tôi. Câu hỏi của tôi liên quan đến sự khác biệt giữa hàm "C" bên ngoài và hàm * thành viên tĩnh *. –

2

Nói chung

lớp lưu trữ:

lớp lưu trữ được sử dụng để chỉ thời gian và phạm vi của một biến hoặc nhận dạng.

Thời gian:

Thời gian cho thấy tuổi thọ của một biến.

Phạm vi:

Phạm vi chỉ ra khả năng hiển thị của biến.

storage class tĩnh:

Lớp lưu trữ tĩnh được sử dụng để khai báo một định danh đó là một biến địa phương hoặc là một hàm hoặc một tập tin và vẫn duy trì và giữ lại giá trị của nó sau khi điều khiển đi từ nơi nó được khai báo. Lớp lưu trữ này có thời lượng là vĩnh viễn. Một biến được khai báo của lớp này giữ lại giá trị của nó từ một hàm của hàm này đến hàm tiếp theo. Phạm vi là địa phương. Một biến chỉ được biết bởi hàm được khai báo bên trong hoặc nếu khai báo trên toàn cầu trong một tệp, nó chỉ được biết hoặc chỉ thấy bởi các hàm trong tệp đó. Lớp lưu trữ này đảm bảo rằng việc khai báo biến cũng khởi tạo biến thành 0 hoặc tất cả các bit.

extern lớp lưu trữ:

Lớp lưu trữ extern được sử dụng để khai báo một biến toàn cầu sẽ được biết đến các chức năng trong một tập tin và khả năng được biết đến tất cả các chức năng trong một chương trình. Lớp lưu trữ này có thời lượng là vĩnh viễn. Bất kỳ biến nào của lớp này vẫn giữ giá trị của nó cho đến khi được thay đổi bởi một nhiệm vụ khác. Phạm vi là toàn cầu. Một biến có thể được biết hoặc nhìn thấy bởi tất cả các hàm trong một chương trình.