The C language provides several different kinds of constants: integer constants, such as 10
and 0x1C
, ; floating constants, such as 1.0
and 6.022e+23
, ; and character constants, such as 'a'
and '\x10'
. C also provides string literals, such as "hello, world"
and "\n"
. These may constants can all be referred to as literals.
Wiki Markup |
---|
Use meaningful symbolic constants, rather than raw values, to represent arbitrary constant values. A well-named symbol adds clarity to the program's source code by conveying the meaning of a constant. Using symbolic constants also simplifies maintenance and promotes portability by providing a single change point. Although some constants represent values that never change, many constants represent arbitrary implementation decisions, such as buffer sizes, that are subject to change. If you use a symbol to represent a constant value, you can change the value throughout the program by changing the symbol definition (the single change point) and rebuilding the program \[[Saks 02|AA. C References#Saks 02]\]. |
Avoid the use of magic numbers in code when possible. Magic numbers are When used in program logic, literals can reduce the readability of source code. As a result, literals, in general, and integer constants, in particular, are frequently called magic numbers because their purpose is often obscured. Magic numbers can be constant values that represent either an arbitrary value (such as a determined appropriate buffer size) or a malleable concept (such as the age at which a person is considered an adult, which could can change between geopolitical boundaries). Rather than embed literals in program logic, use appropriately named symbolic constants to clarify the intent of the code. In addition, if a specific value needs to be changed, reassigning a symbolic constant once is more efficient and less error prone than replacing every instance of the value [Saks 2002].
The C programming language has several mechanisms for defining creating named, symbolic constants: const
-qualified objects, enumeration constants, and object-like macro definitions. Each of these mechanisms has associated advantages and disadvantages.
const
-
...
Qualified Objects
Objects that are const
-qualified have scope and can be type-checked by the compiler. Because these they are named objects (unlike macro definitions), (certain) some debugging tools can show the name of the object. The objects object also consumes memory (though this is not too important). .
A const
-qualified object allows you to specify the exact type of the constant. For example,
Code Block |
---|
const unsigned int buffer_size = 256;
|
defines buffer_size
as a constant whose type is unsigned int
.
Unfortunately, const
-qualified objects cannot be used where compile-time integer constants are required, namely to define the
- size Size of a bit-field member of a structure.
- size Size of an array (except in the case of variable length arrays).
- value Value of an enumeration constant.
- value Value of a
case
constant
Enumeration Constants
- .
If any of these are required, then an integer constant (which would be an rvalue) must be used.
const
-qualified objects allow the programmer to take the address of the object:
Code Block |
---|
const int max = 15;
int a[max]; /* Invalid declaration outside of a function */
const int *p;
/* A const-qualified object can have its address taken */
p = &max;
|
const
-qualified objects are likely to incur some runtime overhead [Saks 2001b]. Most C compilers, for example, allocate memory for const
-qualified objects. const
-qualified objects declared inside a function body can have automatic storage duration. If so, the compiler will allocate storage for the object, and it will be on the stack. As a result, this storage will need to be allocated and initialized each time the containing function is invoked.
Enumeration Constants
Enumeration constants An enumeration constant is a member of an enumeration. Enumeration constant can be used to represent an integer constant expression that has a value representable as an int
. Unlike const
-qualified objects, enumeration constants do not require that consume memory. No storage is allocated for the value, so it is not possible to take the address of an enumeration constant.
Code Block |
---|
enum { max = 15 };
int a[max]; /* OK outside function */
const int *p;
p = &max; /* Error: "&" on enum constant */
|
Enumeration constants do not allow the type of the value to be specified. An enumeration constant whose value can be represented as an int
is always an int
.
Object-like Macros
A preprocessing directive of the form:
#
define
identifier replacement-list
defines an _object-like_ macro that causes each subsequent instance of the macro name to be replaced by the replacement list of preprocessing tokens that constitute the remainder of the directive \[[ISO/IEC 9899-1999|AA. C References#ISO/IEC 9899-1999]\]. Wiki Markup
#define:
- operates at compile time
- consumes no memory (though this is not too important)
- can use in compile-time constant expression
- uses different syntax; can make mistake with ;
- can't create pointers to
- no type checking
const:
- operates at run time
- consumes memory (though this is not too important)
- can't use in compile-time constant expression
- uses consistent syntax
- can create pointers to
- does type checking
If any of these are required, then an integer constant (which would be an rvalue) must be used.
.
C programmers frequently define symbolic constants as object-like macros. For example, the code
Code Block |
---|
#define buffer_size 256
|
defines buffer_size
as a macro whose value is 256. The preprocessor substitutes macros before the compiler does any other symbol processing. Later compilation phases never see macro symbols, such as buffer_size
; they see only the source text after macro substitution. As a result, many compilers do not preserve macro names among the symbols they pass on to their debuggers.
Macro names do not observe the scope rules that apply to other names. Therefore, macros could substitute in unanticipated places with unexpected results.
Object-like macros do not consume memory; consequently, it is not possible to create a pointer to one. Macros do not provide for type checking because they are textually replaced by the preprocessor.
Macros can be passed as compile-time arguments.
Summary
The following table summarizes some of the differences between const
-qualified objects, enumeration constants, and object-like macro definitions.
Method | Evaluated At |
---|
Method
Consumes Memory | Viewable by Debuggers | Type Checking | Compile- |
---|
Time Constant Expression |
---|
Enumerations |
Compile time |
No |
Yes |
Yes |
Yes |
|
Runtime |
Yes |
Yes |
Yes |
No |
Macros |
Preprocessor |
No |
No |
No |
Yes |
...
Noncompliant Code Example
The meaning of the numeric integer literal 18 is not clear in this example.:
Code Block | ||||
---|---|---|---|---|
| ||||
/* ... */
if (age >= 18) {
/* Take action */
}
else {
/* Take a different action */
}
/* ... */
|
Compliant Solution
The This compliant solution replaces the integer literal 18 with the symbolic constant ADULT_AGE
to clarify the meaning of the code.When declaring immutable symbolic values, such as ADULT_AGE
, it is best to declare them as a constant in accordance with DCL00-A. Const-qualify immutable objects.:
Code Block | ||||
---|---|---|---|---|
| ||||
enum { ADULT_AGE=18 };
/* ... */
if (age >= ADULT_AGE) {
/* Take action */
}
else {
/* Take a different action */
}
/* ... */
|
...
Noncompliant Code Example
Magic numbers Integer literals are frequently used when referring to array dimensions, as shown in this non-compliant coding example.noncompliant code example:
Code Block | ||||
---|---|---|---|---|
| ||||
char buffer[256];
/* ... */
fgets(buffer, 256, stdin);
|
This use of magic numbers integer literals can easily result in buffer overflows if, if for example, the buffer size is reduced but the magic number integer literal used in the call to fgets()
is not.
Compliant Solution (enum
)
In this compliant solution, the magic number integer literal is replaced with an enumeration constant. (see See DCL00-AC. Const-qualify immutable objects.).
Code Block | ||||
---|---|---|---|---|
| ||||
enum { BUFFER_SIZE=256 };
char buffer[BUFFER_SIZE];
/* ... */
fgets(buffer, BUFFER_SIZE, stdin);
|
Enumeration constants can safely be used anywhere a constant expression is required.
Compliant Solution (sizeof
)
A Frequently, it is possible to obtain the desired readability by using a symbolic expression composed of existing symbols rather than by defining a new symbol. For example, a sizeof
expression can work just as well as an enumeration constant. (see See EXP09-AC. Use sizeof to determine the size of a type or variable.).
Code Block | ||||
---|---|---|---|---|
| ||||
char buffer[256];
/* ... */
fgets(buffer, sizeof(buffer), stdin);
|
...
Using the {{sizeof
}} expression reduces the total number of names declared in the program, which is almost always a good idea \[[Saks 02|AA. C References#Saks 02]\].
Compliant Solution
expression in this example reduces the total number of names declared in the program, which is generally a good idea [Saks 2002]. The sizeof
operator is almost always evaluated at compile time (except in the case of variable-length arrays).
When working with sizeof()
, keep in mind ARR01-C. Do not apply the sizeof operator to a pointer when taking the size of an array.
Noncompliant Code Example
In this noncompliant code example, the string literal "localhost"
and integer constant 1234
are embedded directly in program logic and are consequently difficult to change:
Code Block | ||||
---|---|---|---|---|
| ||||
LDAP *ld = ldap_init("localhost", 1234);
if (ld == NULL) {
perror("ldap_init");
return(1);
}
|
Compliant Solution
In this compliant solution, the host name and port number are both defined as object-like macros, so they can Constant values that may be passed as compile-time arguments must be macro definitions, as shown by this example:
Code Block | ||||
---|---|---|---|---|
| ||||
#ifndef PORTNUMBER /* Might be passed on compile line */ # define PORTNUMBER 1234 #endif #ifndef MYPORTNUMBERHOSTNAME /* mightMight be passed on compile line */ # define MYPORTNUMBERHOSTNAME 1234 #endif "localhost" #endif /* ... */ LDAP *ld = ldap_init(HOSTNAME, PORTNUMBER); if (ld == NULL) { perror("ldap_init"); return(1); } |
Exceptions
DCL06-C-EX1: While Although replacing numeric constants with a symbolic constant is often a good practice, it can be taken too far. Remember that the goal is to improve readability. Exceptions can be made for constants that are themselves the abstraction you want to represent, as in this compliant solution.
Code Block | ||||
---|---|---|---|---|
| ||||
x = (-b + sqrt(b*b - 4*a*c)) / (2*a);
|
Replacing numeric constants with symbolic constants in this example does nothing to improve the readability of the code , and may in fact can actually make the code more difficult to read:.
Code Block |
---|
enum { TWO = 2 }; /* aA scalar */ enum { FOUR = 4 }; /* aA scalar */ enum { SQUARE = 2 }; /* anAn exponent */ x = (-b + sqrt(pow(b, SQUARE) - FOUR*a*c))/ (TWO * a); |
When implementing recommendations, it is always necessary to use sound judgment.
(Note that this example does not prevent overflow or check for invalid operations (taking the sqrt()
of a negative number). ) See INT32-C. Ensure that operations on signed integers do not result in overflow and FLP32-C. Prevent or detect domain and range errors in math functions for more information on detecting domain and range errors in math functions.
Risk Assessment
Using numeric literals makes code more difficult to read and understand. Buffer overruns are frequently a consequence of a magic number being changed in one place (like such as in an array declaration) but not elsewhere (like such as in a loop through an array).
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
DCL06- |
1 (low)
1 (unlikely)
C | Low | Unlikely | Medium | P2 | L3 |
Automated Detection
...
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Axivion Bauhaus Suite |
| CertC-DCL06 | |||||||
Compass/ROSE | Could detect violations of this recommendation merely by searching for the use of "magic numbers" and magic strings in the code itself. That is, any number (except a few canonical numbers: −1, 0, 1, 2) that appears in the code anywhere besides where assigned to a variable is a magic number and should instead be assigned to a | ||||||||
| CC2.DCL06 | Fully implemented | |||||||
Helix QAC |
| C3120, C3121, C3122, C3123, C3131, C3132 | |||||||
LDRA tool suite |
| 201 S | Fully implemented | ||||||
Parasoft C/C++test |
| CERT_C-DCL06-a | Use meaningful symbolic constants to represent literal values | ||||||
Polyspace Bug Finder |
| Checks for:
Rec. fully covered. |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
...
Use meaningful symbolic constants to represent literal values in program logic | |
MITRE CWE | CWE-547, Use of hard-coded, security-relevant constants |
Bibliography
[Henricson 1992] | Chapter 10, "Constants" |
[Saks 2001a] | |
[Saks 2001b] | |
[Saks 2002] | |
[Summit 2005] | Question 10.5b |
...
References
Wiki Markup |
---|
\[[Henricson 92|AA. C References#Henricson 92]\] Chapter 10, "[Constants|http://www.doc.ic.ac.uk/lab/cplus/c++.rules/chap10.html]"
\[[ISO/IEC 9899-1999|AA. C References#ISO/IEC 9899-1999]\] Section 6.7, "Declarations"
\[[ISO/IEC PDTR 24772|AA. C References#ISO/IEC PDTR 24772]\] "BRS Leveraging human experience"
\[[Saks 01|AA. C References#Saks 01]\] Dan Saks. [Symbolic Constants|http://www.embedded.com/story/OEG20011016S0116]. Embedded Systems Design. November, 2001.
\[[Saks 02|AA. C References#Saks 02]\] Dan Saks. [Symbolic Constant Expressions|http://www.embedded.com/story/OEG20020124S0117]. Embedded Systems Design. February, 2002. |
DCL05-A. Use typedefs to improve code readability 02. Declarations and Initialization (DCL) DCL07-A. Include the appropriate type information in function declarators