2012-06-20 47 views
5

Tôi đã có một vấn đề kỳ lạ mà tôi đã thu hẹp xuống test sau:Truy cập globals tĩnh trong một hàm nội tuyến

inl.h:

inline const char *fn() { return id; } 

a.cc:

#include <stdio.h> 

static const char *id = "This is A"; 

#include "inl.h" 

void A() 
{ 
    printf("In A we get: %s\n", fn()); 
} 

b.cc:

#include <stdio.h> 

static const char *id = "This is B"; 

#include "inl.h" 

void B() 
{ 
    printf("In B we get: %s\n", fn()); 
} 

extern void A(); 

int main() 
{ 
    A(); 
    B(); 
    return 0; 
} 

Bây giờ khi tôi biên dịch này với g++ -O1 a.cc b.cc nó có vẻ hoạt động chính xác. Tôi nhận được:

In A we get: This is A 
In B we get: This is B 

nhưng nếu tôi biên dịch với g++ -O0 a.cc b.cc tôi nhận được:

In A we get: This is A 
In B we get: This is A 

Lưu ý rằng tôi thực sự cố gắng sử dụng ngữ nghĩa C11 ở đây, nhưng tôi sử dụng g ++ như gcc không hỗ trợ C11. Bây giờ theo như tôi thấy, nhìn vào cả thông số C11 và spec C++ (C++ 11 và các thông số cũ hơn - ngữ nghĩa của các hình cầu cục bộ và tĩnh dường như không thay đổi), nó nên làm những gì tôi muốn, và sự thất bại khi sử dụng -O0 là một lỗi trong gcc.

Điều này có đúng không, hoặc có điều gì đó ở đâu đó trong thông số kỹ thuật mà tôi thiếu mà có thể làm cho hành vi không xác định này không?

Sửa

Câu trả lời phổ biến dường như cho rằng fn cần phải được khai báo là static để làm việc này. Nhưng theo 6.7.4.6 của thông số C99 (6.7.4.7 trong thông số C11 - không chắc chắn về thông số C++):

Nếu tất cả các khai báo phạm vi tập tin cho một chức năng trong đơn vị dịch bao gồm inline function specifier không có extern, thì định nghĩa trong đơn vị dịch đó là định nghĩa nội tuyến . Định nghĩa nội tuyến không cung cấp định nghĩa bên ngoài cho hàm, và không cấm định nghĩa bên ngoài trong một đơn vị dịch khác.

Do không có rõ ràng extern ở đây, nên là hai hàm nội tuyến độc lập không có tương tác với nhau. Không yêu cầu static rõ ràng.

Sử dụng sửa lỗi tĩnh rõ ràng vấn đề cho C, nhưng không hoạt động cho hàm thành viên nội tuyến C++, vì từ khóa static có nghĩa hoàn toàn khác trong trường hợp đó.

+0

Không quan tâm, liệu hành vi có được nhân rộng nếu bạn thay thế bao gồm bằng định nghĩa hàm nội tuyến không? Nếu bộ tiền xử lý đang chạy như một thẻ đầu tiên độc lập như tôi tưởng tượng, bạn sẽ có thể đơn giản hóa trường hợp kiểm tra. –

+0

Btw: phiên bản gcc nào? –

+0

gcc 4.5.3 và 4.6.1 đều hoạt động theo cách này. –

Trả lời

8

Bạn đã vi phạm quy tắc một định nghĩa. Hàm không tĩnh fn được định nghĩa khác nhau trong hai đơn vị dịch của bạn. Một liên kết với biến số id được xác định trong a.cc, trong trường hợp biến kia liên kết với biến số id trong b.cc. Các định nghĩa là văn bản giống hệt nhau, nhưng điều đó không đủ để đáp ứng quy tắc một định nghĩa, ngay cả với ngoại lệ được đặt ra cho các hàm được khai báo inline, do đó bạn nhận được hành vi không xác định.

Bạn đang sử dụng trình biên dịch C++, không phải trình biên dịch C, vì vậy bất kỳ điều gì C11 nói là không liên quan đến hành vi mà chương trình C++ của bạn thể hiện. Trong C++ 11, tiêu chuẩn (tại § 3.2/5) dường như nêu quy tắc về cách fn được phép tham khảo id (nhấn mạnh và elip mỏ):

Có thể có nhiều hơn một định nghĩa của một & hellip; inline function với liên kết bên ngoài (7.1.2) & hellip; trong một chương trình miễn là mỗi định nghĩa xuất hiện trong một đơn vị dịch thuật khác, và cung cấp các định nghĩa đáp ứng các yêu cầu sau. Với một thực thể như vậy được đặt tên D quy định tại nhiều hơn một đơn vị dịch thuật, sau đó

  • mỗi định nghĩa của D sẽ bao gồm cùng một chuỗi các thẻ; và
  • trong mỗi định nghĩa của D, tên tương ứng, ngẩng đầu lên theo 3.4, sẽ đề cập đến một thực thể được xác định trong định nghĩa của D, hoặc sẽ đề cập đến cùng một thực thể, sau khi giải quyết tình trạng quá tải (13,3) và sau khi khớp với một phần mẫu chuyên môn hóa (14.8.3), ngoại trừ tên có thể tham chiếu đến một đối tượng const với nội bộ hoặc không có liên kết nếu đối tượng có cùng một chữ nhập vào tất cả các định nghĩa của D và đối tượng được khởi tạo với một biểu thức liên tục (5.19) và giá trị (nhưng không phải là địa chỉ) của 012 Đối tượngđược sử dụng và đối tượng có cùng giá trị trong tất cả các định nghĩa trong số D; và
  • & hellip;

định nghĩa của bạn về fn bao gồm cùng một chuỗi các thẻ, nhưng họ đề cập đến id, mà không được định nghĩa trong D, không phải là cùng một thực thể trong cả hai đơn vị dịch thuật và không có giá trị như nhau trong tất cả các định nghĩa. Tôi thấy không có điều khoản trong tiêu chuẩn C++ cho một hàm nội tuyến có được liên kết nội bộ ngầm định. C++ 11 § 7.1.1/7 nói điều này:

Một tên tuyên bố trong một phạm vi không gian tên mà không có một lưu trữ-lớp-specifier có mối liên hệ bên ngoài trừ khi nó có mối liên kết nội bộ vì một tuyên bố trước đó và cung cấp nó không được khai báo const.

Nếu bạn có hành vi mong đợi ở mức tối ưu nhất định hoặc từ phiên bản nhất định của trình biên dịch nhất định, bạn chỉ nhận được phiên bản bất thường của hành vi không xác định, nơi mọi thứ dường như hoạt động mặc dù bị sai.

+0

Nhưng theo thông số kỹ thuật, hai đơn vị biên dịch đưa ra hai định nghĩa nội bộ độc lập của hàm, không phải là định nghĩa bên ngoài, và không nên tương tác bên ngoài theo bất kỳ cách nào ... –

+0

@ChrisDodd Nó cung cấp hai định nghĩa độc lập của hàm __both trong đó__ có thể nhìn thấy bên ngoài, và trình liên kết được tự do phàn nàn về các định nghĩa trùng lặp, hoặc, có vẻ như trường hợp này đã làm, âm thầm loại bỏ một, giả sử chúng giống hệt nhau (mặc dù chúng không được). Việc khai báo 'fn()' là 'tĩnh' sẽ làm cho chúng trở nên không thể nhìn thấy bên ngoài. – twalberg

+0

@twalberg: xem spec quote ở trên: nó "không cung cấp định nghĩa bên ngoài của hàm" –

3

fn() không được khai báo static, như @RobKennedy chỉ ra, bạn đang mạo hiểm vào vùng đất UB. Trong trường hợp cụ thể này, -O0 có thể vô hiệu hóa nội tuyến, có nghĩa là một trong các phiên bản không được nội tuyến của hàm sẽ được giữ lại và phiên bản còn lại bị loại bỏ và cả hai cuộc gọi sẽ là phiên bản không được nội tuyến. Cho dù phiên bản A luôn được giữ có thể phụ thuộc vào thứ tự mà bạn chỉ định tệp của mình trên dòng lệnh hay bất kỳ số nào khác.-O1 có thể bao gồm nội tuyến, trong trường hợp này, ngay cả khi có bản sao không được nội tuyến của hàm được phát ra, hai cuộc gọi vẫn có thể được inlined, cung cấp kết quả mong đợi (sai).

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