Các biến `static` của phạm vi tệp trong C có tệ như các biến toàn cục` extern` không?


8

Trong C, bạn thường / đôi khi (như một vấn đề về phong cách) sử dụng staticbiến phạm vi tệp trong đó bạn sử dụng biến thành viên lớp riêng trong C ++. Khi nhân rộng các chương trình đa luồng, chỉ cần thêm thread_localvào C11 hoặc phần mở rộng được hỗ trợ dài __threadcũng phù hợp. Tôi biết bạn có thể làm chính xác như trong C ++ bằng cách đặt mọi thứ vào trong a structvà tạo một tập hợp các hàm lấy một con trỏ tới đó structlàm đối số đầu tiên. Một số thư viện làm điều này rộng rãi. Nhưng phong cách cá nhân của tôi là giữ structcàng nhỏ càng tốt, nếu cần.

Tôi thường đọc hoặc nghe một số người tranh luận về các biến 'toàn cầu' rất tệ. Tôi làm theo lý do của họ và hầu hết các lập luận của họ dường như có liên quan đến externcác biến toàn cầu trong thuật ngữ C. Những gì họ nói chắc chắn là đúng. Đôi khi tôi sử dụng 1 hoặc 2 externbiến được khai báo trong toàn bộ chương trình khi nó sẽ đơn giản hóa mọi thứ rất nhiều và khi dễ theo dõi chúng, nhưng đi xa hơn sẽ dễ dàng khiến chương trình không thể đoán trước.

Còn staticcác biến thì sao? Họ vẫn có cùng một vấn đề như các biến toàn cầu 'thực'? Có lẽ tôi thậm chí không phải hỏi câu hỏi này và tiếp tục nếu tôi nghĩ những gì tôi đang làm là đúng, nhưng hôm nay tôi thấy một "biến toàn cầu khác là loại bài đăng BAD", và cuối cùng đến đây nghĩ rằng có lẽ đây là một quyền nơi cho loại câu hỏi như vậy. Bạn nghĩ gì?

Câu hỏi này không phải là một bản sao của này vì câu hỏi này hỏi về externstaticcác đại lượng không địa phương trong khi các câu hỏi khác là về file-phạm vi và khối phạm vi staticbiến.



3
Tôi không nghĩ rằng đây là một bản sao. Câu hỏi này là hỏi về một biến tĩnh tệp so với biến tĩnh (toàn cầu) bên ngoài. Câu hỏi khác là so sánh các biến chức năng-tĩnh với các biến toàn cục.

@gnat Đây không phải là một câu hỏi trùng lặp. Tôi đã thực hiện một chỉnh sửa để giải thích về cơ bản nói những gì Snowman nói trong bình luận của mình. Ngay cả tiêu đề cũng khác nhau.
xiver77

@ xiver77 trong khi hiện tại không có phiếu bầu nào để đóng là trùng lặp, một số câu trả lời cho câu hỏi khác có thể sâu sắc ở đây.

@Snowman theo cách đọc của tôi, câu trả lời được chấp nhận trong câu hỏi khác bao gồm cả ba trường hợp (dường như giải thích khi nào và tại sao thống kê phạm vi tệp được ưu tiên hơn cả phạm vi chức năng và phạm vi toàn cầu)
gnat

Câu trả lời:


17

Trong một chương trình C được thiết kế tốt, một biến tĩnh tệp tương tự như một thành viên tĩnh riêng của một lớp:

  • Nó chỉ có thể được truy cập bởi các hàm trong tệp đó, tương tự như cách một biến thành viên tĩnh riêng chỉ có thể được truy cập bởi các hàm trong lớp mà nó được định nghĩa.

  • Chỉ có một bản sao của biến.

  • Cuộc đời của nó là cuộc đời chương trình.

Một externbiến sẽ là một biến toàn cục thực sự như trong bất kỳ ngôn ngữ nào hỗ trợ chúng.

Một biến statickhông toàn cầu không tệ như toàn cầu; trong thực tế, chúng là cần thiết trong một số trường hợp.

  • Truy cập được kiểm soát thông qua các chức năng bạn viết. Điều này giúp với tính toàn vẹn dữ liệu bao gồm cả kiểm tra giới hạn cũng như an toàn luồng. (lưu ý: điều này không đảm bảo an toàn cho luồng, nó chỉ đơn giản là một công cụ trợ giúp trên đường đi)

  • Dữ liệu được gói gọn: chỉ có tệp đó có thể truy cập được. Điều này gần giống như C có thể nhận được để đóng gói trong đó nhiều hàm có thể truy cập vào một biến tĩnh.

Biến toàn cầu là xấu không có vấn đề gì. Biến tệp tĩnh có lợi ích của biến tĩnh riêng nhưng không có nhược điểm nào của biến toàn cục.

Vấn đề duy nhất không giống với biến tĩnh riêng thực sự như trong C ++, các tệp khác có thể khai báo một externbiến khớp với khai báo và bạn không thể ngăn truy cập. Nói cách khác, bạn đang dựa vào hệ thống danh dự để tránh biến nó thành một biến toàn cầu.


6

Trạng thái toàn cầu, bao gồm externcác biến và không const staticbiến trong phạm vi tệp hoặc trong các hàm có thể thường xuyên là một giải pháp dễ dàng cho một vấn đề nhất định, nhưng có ba vấn đề:

  1. staticlàm cho mã không thể kiểm chứng , bởi vìstatic các biến có xu hướng là các phụ thuộc không thể thay thế. Hoặc nói thêm từ OOP-y: bạn không tuân theo Nguyên tắc đảo ngược phụ thuộc. Tôi đã đến C và C ++ từ các ngôn ngữ động như Perl, vì vậy mô hình chi phí của tôi nghiêng về công văn ảo và con trỏ hàm, v.v. Với các ngôn ngữ hiện tại, có một số mâu thuẫn giữa khả năng kiểm tra và kiến ​​trúc tốt, nhưng tôi nghĩ rằng sự phiền toái nhỏ của việc làm cho sự phụ thuộc của bạn rõ ràng và để chúng bị ghi đè trong các bài kiểm tra được bù đắp đáng kể bằng cách dễ dàng viết bài kiểm tra, và do đó đảm bảo phần mềm của bạn đang hoạt động như hy vọng. Không làm cho mã của bạn năng động hơn, cơ chế duy nhất có sẵn để thêm các phụ thuộc cho kiểm tra là biên dịch có điều kiện.

  2. Nhà nước toàn cầu gây khó khăn cho lý do về tính chính xác , và điều đó dẫn đến lỗi. Càng nhiều bit và mảnh có quyền truy cập vào một biến và có thể sửa đổi nó, càng dễ mất dấu vết của những gì đang xảy ra. Thay vào đó: thích chỉ định các biến! Thích constbất cứ nơi nào hợp lý! Thích bảo vệ các biến thông qua getters và setters nơi bạn có thể giới thiệu kiểm tra tính chính xác. Miễn là nhà nước có staticvà không extern, vẫn có thểđể duy trì sự chính xác, nhưng sẽ tốt hơn nếu cho rằng tôi trong một tuần sẽ không thông minh như tôi ngay bây giờ. Đặc biệt trong C ++, chúng ta có thể sử dụng các lớp để mô hình hóa các khái niệm trừu tượng khác nhau khiến không thể sử dụng sai thứ gì đó, vì vậy hãy thử sử dụng hệ thống loại hơn là trí thông minh của bạn - bạn có nhiều thứ quan trọng hơn để suy nghĩ.

  3. Trạng thái toàn cầu có thể ngụ ý rằng các chức năng của bạn không được cấp lại hoặc chúng chỉ có thể được sử dụng trong một ngữ cảnh tại một thời điểm. Hãy tưởng tượng một trình điều khiển cơ sở dữ liệu chỉ có thể quản lý một kết nối! Đó là một hạn chế hoàn toàn không cần thiết. Trong thực tế, các hạn chế thường là tinh vi hơn, chẳng hạn như một biến toàn cục được sử dụng để tổng hợp kết quả. Thay vào đó, làm cho luồng dữ liệu của bạn rõ ràng và chuyển mọi thứ thông qua các tham số chức năng. Một lần nữa, các lớp C ++ có thể làm cho điều này dễ quản lý hơn.

Rõ ràng static const NAMED_CONSTANTSlà ổn. Sử dụng staticbên trong các hàm phức tạp hơn nhiều: trong khi nó hữu ích cho các hằng số khởi tạo lười biếng, nó có thể khá khó kiểm soát. Một thỏa hiệp là tách riêng việc tính giá trị ban đầu khỏi biến tĩnh, để cả hai phần có thể được kiểm tra riêng.

Trong các chương trình nhỏ, khép kín, tất cả những điều này sẽ không thành vấn đề và bạn có thể tiếp tục sử dụng statictrạng thái để làm cho trái tim của bạn thích thú. Nhưng khi bạn vượt qua khoảng 500 LỘC hoặc nếu bạn đang viết một thư viện có thể sử dụng lại, bạn thực sự nên bắt đầu nghĩ về kiến ​​trúc tốt và giao diện tốt mà không bị hạn chế không cần thiết.


Một số đề xuất của bạn về thiết kế và an toàn đôi khi không thể thực hiện trong các chương trình cần được viết bằng C.
xiver77

Và nhà nước toàn cầu không nhất thiết phải không thân thiện. Cả tiêu chuẩn C và C ++ mới nhất đều có thread_localvà trình biên dịch từ lâu đã hỗ trợ nó như một phần mở rộng trước khi tiêu chuẩn hóa.
xiver77

Để tránh nhiều vấn đề của trạng thái toàn cầu mà bạn đã đề cập, hầu hết các chương trình C được viết tốt đều có cấu trúc nông và minh bạch với các phụ thuộc bên ngoài được giảm thiểu.
xiver77

Giữ mã tĩnh và dữ liệu có thể thay đổi, trong khi dễ bị lỗi, là cách duy nhất để viết chương trình hiệu quả nhất có thể trong kiến ​​trúc máy tính hiện tại.
xiver77

@ xiver77 Tôi rất thích viết mã cấp cao hơn, vì vậy không phải hy sinh kiến ​​trúc để thực hiện hay sử dụng C là cần thiết trong công việc của tôi :) Khi viết C, tôi thấy khó khăn hơn nhiều khi viết mã chính xác và lựa chọn các tóm tắt phù hợp khác với những gì tôi thích, nhưng vẫn có thể cung cấp nhiều sự an toàn. Câu trả lời của tôi cố gắng đánh dấu rõ ràng C ++ - ý tưởng cụ thể như vậy. Quan điểm của bạn về các tệp nhỏ là rất tốt, điều đó giúp dễ dàng suy luận về chương trình và giảm bớt nhưng không loại bỏ được vấn đề do staticcác biến đặt ra .
amon

1

Tôi không coi các biến có phạm vi tệp là xấu như các biến toàn cục. Rốt cuộc, tất cả các truy cập vào các biến này được giới hạn trong một tệp nguồn duy nhất. Với hạn chế đó, các biến phạm vi tệp gần như tốt hoặc xấu như một thành viên dữ liệu tĩnh riêng C ++ và bạn không cấm sử dụng chúng, phải không?


1
Trong c ++, loại cú pháp khuyến khích bạn đặt nhiều biến thành viên trong các lớp, do đó, việc có một thành viên tĩnh riêng thường không cần thiết. Nhưng trong C, nếu bạn sẽ không mô phỏng đầy đủ những gì bạn làm với lớp C ++, các biến tĩnh hoặc thread_local có thể được sử dụng thay cho thành viên cấu trúc nếu bạn muốn tránh đưa con trỏ cấu trúc đi khắp mọi nơi. Câu hỏi của tôi là về điều này.
xiver77

Tôi đang nói về các thành viên với staticbộ lưu trữ, tồn tại chính xác một lần, không phải cho từng đối tượng. Biến này không cần bất kỳ phiên bản nào của lớp để được tham chiếu bởi mã của lớp, do đó không cần phải chuyển xung quanh một tham chiếu / con trỏ tới một singleton. Đây chính xác là những gì các biến phạm vi tập tin đạt được là tốt. Sự khác biệt duy nhất là, staticthành viên có phạm vi lớp trong khi biến static"toàn cầu" có phạm vi tệp. Nhưng hai phạm vi này rất giống nhau về phạm vi, đó là quan điểm của tôi. Tôi đồng ý rằng các ý nghĩa khác nhau staticlà khó hiểu, mặc dù.
cmaster - phục hồi monica

1
Trong C ít nhất, static thực sự có một ý nghĩa cho các biến (không phải cho các hàm).
xiver77

1

Tất cả đều gắn liền với phạm vi của biến (không phải là hằng số, một cái gì đó có thể thay đổi) theo quan điểm của tôi. Đó là một quan điểm thừa nhận thiếu một số sắc thái, nhưng đó là một đối trọng thực tế và hấp dẫn để làm việc trở lại những nguyên tắc cơ bản nhất đối với những người nói, "Điều này hoàn toàn xấu xa!" chỉ sau đó vấp phải những vấn đề tương tự như những vấn đề liên quan đến những gì họ chỉ trích, như điều kiện chủng tộc.

Hãy tưởng tượng bạn có một hàm 50.000 dòng với tất cả các loại biến được khai báo ở trên cùng và các gotocâu lệnh để nhảy khắp nơi. Điều đó không dễ chịu lắm với phạm vi biến số quái dị như vậy, và cố gắng suy luận về chức năng, và những gì đang xảy ra với các biến như vậy, sẽ vô cùng khó khăn. Trong một trường hợp quái dị như vậy, sự phân biệt bình thường giữa tác dụng phụ "bên ngoài" và "bên trong" làm mất rất nhiều mục đích thực tế của nó.

Hãy tưởng tượng bạn có một chương trình đơn giản 80 dòng bạn chỉ cần viết một lần và tạo ra một biến toàn cục (có liên kết nội bộ và phạm vi tệp hoặc liên kết bên ngoài, nhưng cả hai cách chương trình đều rất trẻ). Nó không đến nỗi tệ.

Hãy tưởng tượng bạn có một lớp quái dị trong một ngôn ngữ hướng đối tượng có chứa toàn bộ logic chương trình của bạn với hàng ngàn và hàng ngàn dòng mã để thực hiện. Trong trường hợp đó, các biến thành viên của nó có nhiều vấn đề hơn so với toàn cầu trong chương trình 80 dòng trên.

Nếu bạn muốn có thể lý giải tốt hơn và tự tin hơn về mã của mình, tính an toàn của luồng (hoặc thiếu), hãy chắc chắn hơn, đảm bảo các xét nghiệm của bạn có độ bao phủ tốt mà không bị bỏ sót các trường hợp cạnh tiềm năng, v.v. , sau đó nó giúp thu hẹp quyền truy cập vào các biến.

Thống kê phạm vi tệp sẽ có xu hướng hẹp hơn phạm vi liên kết ngoài, nhưng sau đó một lần nữa nếu tệp nguồn của bạn là 100.000 dòng mã, vẫn còn khá rộng. Vì vậy, đối với các thống kê phạm vi tệp, nếu bạn không thể tránh chúng, tôi sẽ cố gắng thu hẹp phạm vi của chúng bằng cách không làm cho tệp nguồn của bạn có thể truy cập chúng thành ginormous, vì trong trường hợp đó, việc giảm phạm vi của nó là về việc giảm kích thước và phạm vi thiết kế của tệp nguồn, trái với chức năng (đối với biến cục bộ, bao gồm tham số), lớp (đối với biến thành viên) hoặc có thể mô-đun (đối với toàn cầu có liên kết ngoài nhưng chỉ có thể truy cập trong mô-đun) hoặc thậm chí toàn bộ phần mềm (đối với toàn cầu liên kết bên ngoài có thể truy cập vào toàn bộ phần mềm).

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.