biến tĩnh trong một hàm nội tuyến


83

Tôi có một hàm được khai báo và xác định trong tệp tiêu đề. Đây là một vấn đề của chính nó. Khi hàm đó không được nội tuyến, mọi đơn vị dịch sử dụng tiêu đề đó sẽ nhận được một bản sao của hàm và khi chúng được liên kết với nhau sẽ có bản sao. Tôi đã "khắc phục" điều đó bằng cách làm cho hàm nội tuyến, nhưng tôi e rằng đây là một giải pháp mong manh vì theo như tôi biết, trình biên dịch không đảm bảo nội tuyến, ngay cả khi bạn chỉ định từ khóa "nội tuyến". Nếu điều này không đúng, xin vui lòng sửa cho tôi.

Tuy nhiên, câu hỏi thực sự là, điều gì xảy ra với các biến tĩnh bên trong hàm này? Tôi có bao nhiêu bản sao?

Câu trả lời:


105

Tôi đoán bạn đang thiếu một cái gì đó, ở đây.

chức năng tĩnh?

Khai báo một hàm static sẽ làm cho nó "ẩn" trong đơn vị biên dịch của nó.

Tên có phạm vi không gian tên (3.3.6) có liên kết nội bộ nếu nó là tên của

- một biến, hàm hoặc mẫu hàm được khai báo rõ ràng là tĩnh;

3.5 / 3 - C ++ 14 (n3797)

Khi một tên có liên kết nội bộ, thực thể mà nó biểu thị có thể được gọi bằng các tên từ các phạm vi khác trong cùng một đơn vị dịch.

3.5 / 2 - C ++ 14 (n3797)

Nếu bạn khai báo hàm tĩnh này trong tiêu đề, thì tất cả các đơn vị biên dịch bao gồm tiêu đề này sẽ có bản sao riêng của hàm.

Vấn đề là, nếu có các biến tĩnh bên trong hàm đó, thì mỗi đơn vị biên dịch bao gồm tiêu đề này cũng sẽ có phiên bản cá nhân của riêng chúng.

hàm nội tuyến?

Khai báo nội dòng làm cho nó trở thành một ứng cử viên cho nội tuyến (ngày nay nó không có nhiều ý nghĩa trong C ++, vì trình biên dịch sẽ nội tuyến hoặc không, đôi khi bỏ qua thực tế là từ khóa nội dòng có hay không):

Một khai báo hàm (8.3.5, 9.3, 11.3) với một trình xác định nội tuyến khai báo một hàm nội tuyến. Bộ chỉ định nội tuyến chỉ ra cho việc triển khai rằng việc thay thế nội tuyến của thân hàm tại điểm gọi được ưu tiên hơn cơ chế gọi hàm thông thường. Không cần triển khai để thực hiện thay thế nội tuyến này tại điểm gọi; tuy nhiên, ngay cả khi việc thay thế nội tuyến này bị bỏ qua, các quy tắc khác cho các hàm nội tuyến được xác định bởi 7.1.2 sẽ vẫn được tôn trọng.

7.1.2 / 2 - C ++ 14 (n3797)

Trong tiêu đề, nó có một tác dụng phụ thú vị: Hàm nội tuyến có thể được xác định nhiều lần trong cùng một mô-đun và trình liên kết sẽ chỉ cần nối "chúng" thành một (nếu chúng không được nội tuyến vì lý do của trình biên dịch).

Đối với các biến tĩnh được khai báo bên trong, tiêu chuẩn cho biết cụ thể có một và chỉ một trong số chúng:

Một biến cục bộ tĩnh trong một hàm nội tuyến bên ngoài luôn tham chiếu đến cùng một đối tượng.

7.1.2 / 4 - C ++ 98 / C ++ 14 (n3797)

(các hàm theo mặc định là extern, vì vậy, trừ khi bạn đánh dấu cụ thể hàm của mình là tĩnh, điều này sẽ áp dụng cho hàm đó)

Điều này có lợi thế là "tĩnh" (nghĩa là nó có thể được xác định trong tiêu đề) mà không có sai sót (nó tồn tại nhiều nhất một lần nếu nó không được nội dòng)

biến cục bộ tĩnh?

Các biến cục bộ tĩnh không có liên kết (chúng không thể được gọi bằng tên bên ngoài phạm vi của chúng), nhưng có thời lượng lưu trữ tĩnh (tức là nó là toàn cục, nhưng việc xây dựng và phá hủy nó tuân theo các quy tắc cụ thể).

tĩnh + nội tuyến?

Việc trộn nội tuyến và tĩnh sau đó sẽ dẫn đến hậu quả như bạn đã mô tả (ngay cả khi hàm được nội dòng, biến tĩnh bên trong sẽ không có và bạn sẽ kết thúc với nhiều biến tĩnh như bạn có các đơn vị biên dịch bao gồm định nghĩa về các hàm tĩnh của bạn ).

Câu trả lời cho câu hỏi bổ sung của tác giả

Kể từ khi tôi viết câu hỏi, tôi đã thử nó với Visual Studio 2008. Tôi đã cố gắng bật tất cả các tùy chọn làm cho VS hoạt động tuân thủ các tiêu chuẩn, nhưng có thể tôi đã bỏ sót một số tùy chọn. Đây là những kết quả:

Khi hàm chỉ đơn thuần là "nội tuyến", chỉ có một bản sao của biến tĩnh.

Khi hàm là "nội tuyến tĩnh", có bao nhiêu bản sao có đơn vị dịch.

Câu hỏi thực sự bây giờ là liệu mọi thứ được cho là theo cách này, hay đây là một đặc điểm riêng của trình biên dịch Microsoft C ++.

Vì vậy, tôi cho rằng bạn có một cái gì đó như thế:

void doSomething()
{
   static int value ;
}

Bạn phải nhận ra rằng biến static bên trong hàm, nói một cách đơn giản, là một biến toàn cục ẩn đối với tất cả trừ phạm vi của hàm, nghĩa là chỉ hàm được khai báo bên trong mới có thể tiếp cận được.

Nội tuyến hàm sẽ không thay đổi bất cứ điều gì:

inline void doSomething()
{
   static int value ;
}

Sẽ chỉ có một biến toàn cục ẩn. Thực tế là trình biên dịch sẽ cố gắng nội dòng mã sẽ không thay đổi thực tế là chỉ có một biến ẩn toàn cầu.

Bây giờ, nếu hàm của bạn được khai báo là static:

static void doSomething()
{
   static int value ;
}

Sau đó, nó là "riêng tư" cho mỗi đơn vị biên dịch, có nghĩa là mọi tệp CPP bao gồm tiêu đề nơi hàm tĩnh được khai báo sẽ có bản sao riêng của hàm, bao gồm bản sao riêng của biến ẩn toàn cầu, do đó, càng nhiều biến có các đơn vị biên dịch bao gồm tiêu đề.

Thêm "nội tuyến" vào một hàm "tĩnh" với một biến "tĩnh" bên trong:

inline static void doSomething()
{
   static int value ;
}

có cùng kết quả so với việc không thêm từ khóa "nội tuyến" này, liên quan đến biến tĩnh bên trong.

Vì vậy, hành vi của VC ++ là đúng, và bạn đang nhầm ý nghĩa thực sự của "nội tuyến" và "tĩnh".


Tôi nghĩ rằng bạn đang thiếu một điểm quan trọng cần đề cập, rằng ở giai đoạn liên kết, tất cả các biến tĩnh được khai báo trong hàm nội tuyến sẽ được giải quyết thành một, tôi có nhầm không?
user14416

1
Không, bởi vì mỗi biến tĩnh nằm bên trong một hàm riêng biệt của chính nó: Mặc dù thực tế là các hàm có cùng tên, chúng có liên kết nội bộ và do đó, không được chia sẻ qua các đơn vị dịch.
paercebal

1
@paercebal trong inline void doSomething() { static int value ; }, hàm có liên kết bên ngoài; đây là một vi phạm ODR nếu nó xuất hiện trong tiêu đề được bao gồm từ hai đơn vị khác nhau
MM

@MM ý bạn là gì? Chức năng của bạn là inline, nó không thể vi phạm ODR.
Ruslan

@Ruslan đó là một người không có trình tự
MM

39

Tôi tin rằng trình biên dịch tạo ra nhiều bản sao của biến, nhưng trình liên kết chọn một bản và làm cho tất cả các bản khác tham chiếu đến nó. Tôi đã có kết quả tương tự khi thử một thử nghiệm để tạo các phiên bản khác nhau của một hàm nội tuyến; nếu hàm thực sự không được nội tuyến (chế độ gỡ lỗi), thì tất cả các lệnh gọi đi đến cùng một hàm bất kể chúng được gọi từ tệp nguồn nào.

Hãy suy nghĩ như một trình biên dịch trong một khoảnh khắc - làm sao có thể khác được? Mỗi đơn vị biên dịch (tệp nguồn) độc lập với các đơn vị khác và có thể được biên dịch riêng biệt; do đó mỗi người phải tạo một bản sao của biến, nghĩ rằng đó là biến duy nhất. Trình liên kết có khả năng tiếp cận qua các ranh giới đó và điều chỉnh các tham chiếu cho cả biến và hàm.


2
AFAICT, bạn hoàn toàn chính xác trong những gì bạn đang nói ở đây. Tôi không hiểu tại sao mọi người lại bỏ phiếu cho câu trả lời này. Dự đoán duy nhất của tôi là họ đọc đến "nhiều bản sao của biến", và sau đó dừng lại! :( Dù sao thì một mã thông báo (+1) từ tôi.
Richard Corden

3
Khi mọi người hỏi về trình biên dịch, họ có nghĩa là trình biên dịch + trình liên kết, vì bạn không thể chạy các tệp đối tượng. Vì vậy, câu trả lời này đúng nhưng hoàn toàn vô nghĩa.
Evan Dark,

1
Bởi vì mọi người thiếu hiểu biết, đây là câu hỏi nâng cao hơn và tất cả đều phải phân biệt trong cuộc thảo luận.
Sogartar

13

Tôi thấy câu trả lời của Mark Ransom hữu ích - rằng trình biên dịch tạo ra nhiều bản sao của biến tĩnh, nhưng trình liên kết chọn một và thực thi nó trên tất cả các đơn vị dịch.

Ở những nơi khác tôi tìm thấy điều này:

Xem [dcl.fct.spec] / 4

[..] Một hàm nội tuyến với liên kết bên ngoài sẽ có cùng một địa chỉ trong tất cả các đơn vị dịch. Một biến cục bộ tĩnh trong một hàm nội tuyến bên ngoài luôn tham chiếu đến cùng một đối tượng. Một chuỗi ký tự trong một hàm nội tuyến bên ngoài là cùng một đối tượng trong các đơn vị dịch khác nhau.

Tôi không có bản sao của tiêu chuẩn để kiểm tra, nhưng nó khớp với kinh nghiệm của tôi khi kiểm tra lắp ráp trong VS Express 2008


5

Nó được cho là theo cách này. "static" cho trình biên dịch biết rằng bạn muốn hàm là cục bộ cho đơn vị biên dịch, do đó bạn muốn một bản sao cho mỗi đơn vị biên dịch và một bản sao của các biến tĩnh trên mỗi phiên bản của hàm.

"inline" được sử dụng để cho trình biên dịch biết bạn muốn hàm được nội tuyến; ngày nay, nó chỉ được coi là "không sao nếu có nhiều bản sao của mã, chỉ cần đảm bảo rằng nó có cùng chức năng". Vì vậy, mọi người chia sẻ các biến tĩnh.

Lưu ý: câu trả lời này được viết để đáp lại câu trả lời mà người đăng ban đầu đã đăng cho chính mình.


1
Anh ta đang hỏi về 'biến tĩnh' trong 'hàm nội tuyến', không phải biến trong hàm tĩnh.
Richard Corden 9/10/08

Chúng tôi đồng ý về điều đó, nhưng bạn nói đúng: cần chỉnh sửa để đưa câu trả lời trở lại ngữ cảnh.
Raphaël Saint-Pierre

Tôi cũng đã xem qua điều này . Vậy nó là cái nào? inlinekhiến hàm bị nội dòng hoặc có nhiều bản sao không?
Vassilis,

@Vassilis đều đúng, mặc dù inlinekhông gây ra nội dòng, nó chỉ gợi ý nó và nó cho phép nhiều hơn một định nghĩa (nhưng không trong cùng một đơn vị biên dịch).
Raphaël Saint-Pierre

3

Kể từ khi tôi viết câu hỏi, tôi đã thử nó với Visual Studio 2008. Tôi đã cố gắng bật tất cả các tùy chọn làm cho VS hoạt động tuân thủ các tiêu chuẩn, nhưng có thể tôi đã bỏ sót một số tùy chọn. Đây là những kết quả:

Khi hàm chỉ đơn thuần là "nội tuyến", chỉ có một bản sao của biến tĩnh.

Khi hàm là "nội tuyến tĩnh", có bao nhiêu bản sao có đơn vị dịch.

Câu hỏi thực sự bây giờ là liệu mọi thứ được cho là theo cách này, hay đây là sự đồng bộ hóa lý tưởng của trình biên dịch Microsoft C ++.


1
"Khi hàm là" nội tuyến tĩnh "," - Bài đăng ban đầu của bạn không nói gì về việc làm đó. Bạn nên mong đợi các kết quả khác nhau vì static trên một hàm có ý nghĩa khác với static trên một biến. tĩnh trên một hàm nghĩa là các đơn vị dịch khác sẽ không thấy định nghĩa này.
Lập trình viên Windows

không chắc chắn về cài đặt của bạn nhưng trình biên dịch đang hoạt động chính xác trong trường hợp này. Tuy nhiên, bạn có thể muốn bao gồm một bài kiểm tra đơn vị, trong trường hợp bạn gặp phải một số trình biên dịch không phù hợp.
Robert Gould

-1

Nội tuyến có nghĩa là mã thực thi (hướng dẫn) được nội tuyến vào mã của hàm gọi. Trình biên dịch có thể chọn làm điều đó bất kể bạn có yêu cầu hay không. Điều đó không ảnh hưởng đến các biến (dữ liệu) được khai báo trong hàm.


-1

Bên cạnh bất kỳ vấn đề thiết kế nào, tất cả điều này có thể ngụ ý, vì bạn đã bị mắc kẹt với nó, bạn nên sử dụng static trong trường hợp này không phải nội tuyến. Bằng cách đó, mọi người chia sẻ các biến giống nhau. (Chức năng tĩnh)


-2

Tôi tin rằng bạn sẽ kết thúc với một trong mỗi đơn vị dịch. Bạn đã có nhiều phiên bản của hàm đó (và biến tĩnh được khai báo của nó), một phiên bản cho mọi đơn vị dịch bao gồm tiêu đề.


-2

Static có nghĩa là một bản sao được phân phối trong toàn bộ chương trình, nhưng nội tuyến có nghĩa là nó yêu cầu cùng một đoạn mã trong nhiều thời điểm trong cùng một chương trình, vì vậy không thể tạo một biến tĩnh bên trong hàm nội tuyến.

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.