Định nghĩa hằng số toàn cục trong C ++


81

Tôi muốn xác định một hằng số trong C ++ để hiển thị trong một số tệp nguồn. Tôi có thể hình dung những cách sau để xác định nó trong tệp tiêu đề:

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. Một số chức năng lưu lại giá trị (ví dụ int get_GLOBAL_CONST_VAR())
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR; và trong một tệp nguồn const int GLOBAL_CONST_VAR = 0xFF;

Tùy chọn (1) - chắc chắn không phải là tùy chọn bạn muốn sử dụng

Tùy chọn (2) - xác định phiên bản của biến trong mỗi tệp đối tượng bằng cách sử dụng tệp tiêu đề

Tùy chọn (3) - IMO giết chết quá mức trong hầu hết các trường hợp

Tùy chọn (4) - trong nhiều trường hợp có thể không tốt vì enum không có kiểu cụ thể (C ++ 0X sẽ thêm khả năng xác định kiểu)

Vì vậy, trong hầu hết các trường hợp, tôi cần phải chọn giữa (5) và (6). Những câu hỏi của tôi:

  1. Bạn thích (5) hay (6) hơn?
  2. Tại sao (5) là ok, trong khi (2) thì không?

1
5 so với 2: "const" ngụ ý liên kết nội bộ. Khi bạn bao gồm tiêu đề phiên bản 5 này vào nhiều đơn vị dịch, bạn sẽ không vi phạm "quy tắc một định nghĩa". Ngoài ra, const cho phép trình biên dịch thực hiện "liên tục gấp" trong khi giá trị của biến không phải const có thể thay đổi. Phương án 6 sai. Bạn cũng cần "extern" trong tệp cpp để buộc liên kết bên ngoài, nếu không bạn sẽ gặp lỗi trình liên kết. Phương án 6 có ưu điểm là ẩn giá trị. Nhưng nó cũng khiến việc gấp liên tục không thể thực hiện được.
sellibitze

Câu trả lời:


32

(5) nói chính xác những gì bạn muốn nói. Thêm vào đó, nó cho phép trình biên dịch tối ưu hóa nó hầu hết thời gian. (6) mặt khác sẽ không để trình biên dịch tối ưu hóa nó đi vì trình biên dịch không biết liệu cuối cùng bạn sẽ thay đổi nó hay không.


1
OTOH, 5 về mặt kỹ thuật là bất hợp pháp vì vi phạm ODR. Tuy nhiên, hầu hết các trình biên dịch sẽ bỏ qua nó.
Joel

Eh, tôi thích nghĩ về nó như là không có định nghĩa gì cả, tôi chỉ bảo trình biên dịch đặt một cái tên đẹp cho một số. Đối với tất cả các ý định và mục đích đó là (5), có nghĩa là không có chi phí nào trong thời gian chạy.
Blindy

2
(5) có vi phạm ODR không? Nếu đúng, thì (6) được ưu tiên hơn. Tại sao trình biên dịch "không biết nếu bạn sẽ thay đổi nó" trong trường hợp (6)? extern const int ...const int ...cả hai đều không đổi phải không?
D.Shawley

4
AFAIK, từ 5) đến 6), chỉ 6) được phép khi kiểu của hằng không dựa trên int.
Klaim

11
Không có vi phạm ODR, các đối tượng không đổi là tĩnh theo mặc định.
avakar

71

Chắc chắn đi với tùy chọn 5 - loại này an toàn và cho phép trình biên dịch tối ưu hóa (không lấy địa chỉ của biến đó :) Ngoài ra nếu nó nằm trong tiêu đề - hãy gắn nó vào một không gian tên để tránh làm ô nhiễm phạm vi toàn cầu:

// header.hpp
namespace constants
{
    const int GLOBAL_CONST_VAR = 0xFF;
    // ... other related constants

} // namespace constants

// source.cpp - use it
#include <header.hpp>
int value = constants::GLOBAL_CONST_VAR;

3
Tôi gặp lỗi xác định lại khi cố gắng đưa header.hppvào một số tệp nguồn.
LRDPRDX

Không chắc tại sao điều này vẫn nhận được sự ủng hộ - đã gần mười năm, nhưng ngày nay chúng ta đã constexprvà đang đánh các bảng liệt kê cho những thứ như vậy.
Nikolai Fetissov

23

(5) là "tốt hơn" so với (6) vì nó được định nghĩa GLOBAL_CONST_VARlà Biểu thức Hằng số Tích phân (ICE) trong tất cả các đơn vị dịch. Ví dụ: bạn sẽ có thể sử dụng nó làm kích thước mảng và làm nhãn chữ hoa trong tất cả các đơn vị dịch. Trong trường hợp (6) GLOBAL_CONST_VARsẽ là ICE chỉ trong đơn vị dịch mà nó được xác định và chỉ sau điểm định nghĩa. Trong các đơn vị dịch thuật khác, nó sẽ không hoạt động như ICE.

Tuy nhiên, hãy nhớ rằng (5) cung cấp GLOBAL_CONST_VARliên kết nội bộ, nghĩa là "danh tính địa chỉ" của GLOBAL_CONST_VARsẽ khác nhau trong mỗi đơn vị dịch, tức là &GLOBAL_CONST_VARsẽ cung cấp cho bạn một giá trị con trỏ khác nhau trong mỗi đơn vị dịch. Trong hầu hết các trường hợp sử dụng, điều này không quan trọng, nhưng nếu bạn cần một đối tượng hằng số có "định danh địa chỉ" toàn cầu nhất quán, thì bạn phải đi với (6), hy sinh ICE-ness của hằng số trong quá trình.

Ngoài ra, khi ICE-ness của hằng số không phải là một vấn đề (không phải là một kiểu tích phân) và kích thước của kiểu lớn hơn (không phải là một kiểu vô hướng), thì (6) thường trở thành một cách tiếp cận tốt hơn (5).

(2) không được vì GLOBAL_CONST_VARtrong (2) có liên kết bên ngoài theo mặc định. Nếu bạn đặt nó trong tệp tiêu đề, bạn thường sẽ có nhiều định nghĩa GLOBAL_CONST_VAR, đó là một lỗi. constCác đối tượng trong C ++ có liên kết nội bộ theo mặc định, đó là lý do tại sao (5) hoạt động (và đó là lý do tại sao, như tôi đã nói ở trên, bạn có được một liên kết riêng biệt, độc lập GLOBAL_CONST_VARtrong mỗi đơn vị dịch).


Bắt đầu từ C ++ 17, bạn có một tùy chọn khai báo

inline extern const int GLOBAL_CONST_VAR = 0xFF;

trong một tệp tiêu đề. Điều này cung cấp cho bạn ICE trong tất cả các đơn vị dịch (giống như phương pháp (5)) đồng thời duy trì danh tính địa chỉ toàn cầu của GLOBAL_CONST_VAR- trong tất cả các đơn vị dịch, nó sẽ có cùng một địa chỉ.


8

Nếu bạn sử dụng C ++ 11 trở lên, hãy thử sử dụng hằng số thời gian biên dịch:

constexpr int GLOBAL_CONST_VAR{ 0xff };

1
IMHO, đây là giải pháp thỏa mãn duy nhất cho vấn đề này.
lanoxx

5

Nếu nó sẽ là một hằng số thì bạn nên đánh dấu nó là một hằng số - đó là lý do tại sao 2 là xấu theo quan điểm của tôi.

Trình biên dịch có thể sử dụng tính chất const của giá trị để mở rộng một số phép toán và thực sự là các phép toán khác sử dụng giá trị.

Sự lựa chọn giữa 5 và 6 - hmm; 5 chỉ cảm thấy tốt hơn với tôi.

Trong 6) giá trị bị tách ra khỏi khai báo của nó một cách không cần thiết.

Tôi thường sẽ có một hoặc nhiều tiêu đề chỉ định nghĩa các hằng số, v.v. bên trong chúng, và sau đó không có thứ 'thông minh' nào khác - các tiêu đề nhẹ đẹp có thể dễ dàng được đưa vào bất cứ đâu.


3
(6) không phải là sự tách rời không cần thiết, đó là một sự lựa chọn có chủ đích. Nếu bạn có nhiều hằng số lớn, bạn sẽ lãng phí rất nhiều dung lượng trong tệp thực thi nếu bạn không khai báo chúng như trong (6). Điều đó có thể xảy ra trong các thư viện toán học ... sự lãng phí có thể dưới 100 nghìn nhưng đôi khi điều đó cũng quan trọng. (Một số trình biên dịch có những cách khác để giải quyết vấn đề này, tôi nghĩ MSVC có thuộc tính "đã từng" hoặc một cái gì đó tương tự.)
Dan Olson 15/02/10

Với (5), bạn không thể chắc chắn rằng nó sẽ vẫn là const (bạn luôn có thể loại bỏ hằng số). Đây là lý do tại sao tôi vẫn thích loại enum hơn.
fmuecke

@Dan Olson - đó là một điểm rất tốt - Câu trả lời của tôi dựa trên thực tế rằng kiểu liên quan ở đây là int; nhưng khi xử lý các giá trị lớn hơn thì khai báo extern thực sự là một kế hoạch tốt hơn.
Andras Zoltan

@fmuecke - Vâng, bạn nói đúng - trong trường hợp đó giá trị enum ngăn chặn điều này. Nhưng điều đó có nghĩa là chúng ta nên luôn bảo vệ các giá trị của mình khỏi những bài viết theo cách này? Nếu một lập trình viên muốn lạm dụng mã, có rất nhiều khu vực mà một (target_type *) ((void *) & value) có thể tàn phá mà chúng ta không thể nắm bắt được, đến nỗi đôi khi chúng ta phải đặt niềm tin vào chúng; và thực sự là chúng ta, không?
Andras Zoltan

@fmuecke Một biến được khai báo là const không thể thay đổi bởi chương trình (cố gắng làm như vậy là hành vi không xác định). const_cast chỉ được định nghĩa trong các trường hợp mà biến ban đầu không được khai báo là const (ví dụ: truyền một giá trị không phải const vào một hàm dưới dạng const &).
David Stone,

5

Để trả lời câu hỏi thứ hai của bạn:

(2) là bất hợp pháp vì vi phạm Quy tắc Một Định nghĩa. Nó xác định GLOBAL_CONST_VARtrong mọi tệp nơi nó được bao gồm, tức là nhiều hơn một lần. (5) là hợp pháp vì nó không tuân theo Quy tắc Một Định nghĩa. Mỗi GLOBAL_CONST_VARđịnh nghĩa là một định nghĩa riêng biệt, cục bộ cho tệp đó khi nó được bao gồm. Tất nhiên, tất cả các định nghĩa đó đều có chung tên và giá trị, nhưng địa chỉ của chúng có thể khác nhau.


4

C ++ 17 inline biến

Tính năng C ++ 17 tuyệt vời này cho phép chúng tôi:

  • thuận tiện chỉ sử dụng một địa chỉ bộ nhớ duy nhất cho mỗi hằng số
  • lưu trữ nó như một constexpr: Làm thế nào để khai báo constexpr extern?
  • làm điều đó trong một dòng duy nhất từ ​​một tiêu đề

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Biên dịch và chạy:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub ngược dòng .

Xem thêm: Biến nội tuyến hoạt động như thế nào?

Tiêu chuẩn C ++ về các biến nội tuyến

Tiêu chuẩn C ++ đảm bảo rằng các địa chỉ sẽ giống nhau. Bản nháp tiêu chuẩn C ++ 17 N4659 10.1.6 "Bộ chỉ định nội tuyến":

6 Một hàm nội tuyến hoặc một biến có 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.

cppreference https://en.cppreference.com/w/cpp/language/inline giải thích rằng nếustatic không được cung cấp, thì nó có liên kết bên ngoài.

Triển khai biến nội tuyến

Chúng ta có thể quan sát cách nó được triển khai với:

nm main.o notmain.o

trong đó có:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

man nmnói về u:

"u" Biểu tượng là một biểu tượng toàn cầu duy nhất. Đây là phần mở rộng GNU cho bộ tiêu chuẩn của các ràng buộc ký hiệu ELF. Đối với một ký hiệu như vậy, trình liên kết động sẽ đảm bảo rằng trong toàn bộ quá trình chỉ có một ký hiệu có tên và kiểu này được sử dụng.

vì vậy chúng tôi thấy rằng có một phần mở rộng ELF dành riêng cho việc này.

Đã thử nghiệm trên GCC 7.4.0, Ubuntu 18.04.


2
const int GLOBAL_CONST_VAR = 0xFF;

bởi vì nó là một hằng số!


1
Và nó sẽ không được coi như một macro, giúp việc gỡ lỗi với nó dễ dàng hơn.
kayleeFrye_onDeck

-1, Điều này sẽ dẫn đến cảnh báo / lỗi định nghĩa lại khi bao gồm tiêu đề trong nhiều tệp nguồn. Câu trả lời này cũng trùng lặp với câu trả lời của Nikolai Fetissov.
lanoxx 20/09/2018

1

Nó phụ thuộc vào yêu cầu của bạn. (5) là tốt nhất cho việc sử dụng bình thường nhất, nhưng thường dẫn đến liên tục chiếm không gian lưu trữ trong mọi tệp đối tượng. (6) có thể giải quyết vấn đề này trong những tình huống quan trọng.

(4) cũng là một lựa chọn phù hợp nếu ưu tiên của bạn là đảm bảo rằng không gian lưu trữ không bao giờ được phân bổ, nhưng tất nhiên nó chỉ hoạt động cho các hằng số tích phân.


1
#define GLOBAL_CONST_VAR 0xFF // this is C code not C++
int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled
Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision
enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration)
const int GLOBAL_CONST_VAR = 0xFF; // it is the best
extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this
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.