Immutable objects should be const
-qualified. Enforcing object immutability using const
qualification helps ensure the correctness and security of applications. ISO/IEC TR 24772, for example, recommends labeling parameters as constant to avoid the unintentional modification of function arguments [ISO/IEC TR 24772]. STR05-C. Use pointers to const when referring to string literals describes a specialized case of this recommendation.
Adding const
qualification may propagate through a program; as you add const
, qualifiers become still more necessary. This phenomenon is sometimes called const
poisoning, which can frequently lead to violations of EXP05-C. Do not cast away a const qualification. Although const
qualification is a good idea, the costs may outweigh the value in the remediation of existing code.
A macro or an enumeration constant may also be used instead of a const
-qualified object. DCL06-C. Use meaningful symbolic constants to represent literal values describes the relative merits of using const
-qualified objects, enumeration constants, and object-like macros. However, adding a const
qualifier to an existing variable is a better first step than replacing the variable with an enumeration constant or macro because the compiler will issue warnings on any code that changes your const
-qualified variable. Once you have verified that a const
-qualified variable is not changed by any code, you may consider changing it to an enumeration constant or macro, as best fits your design.
Noncompliant Code Example
In this noncompliant code, pi
is declared as a float
. Although pi is a mathematical constant, its value is not protected from accidental modification.
float pi = 3.14159f; float degrees; float radians; /* ... */ radians = degrees * pi / 180;
Compliant Solution
In this compliant solution, pi
is declared as a const
-qualified object:
const float pi = 3.14159f; float degrees; float radians; /* ... */ radians = degrees * pi / 180;
Risk Assessment
Failing to const
-qualify immutable objects can result in a constant being modified at runtime.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
DCL00-C | Low | Unlikely | High | P1 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Astrée | 24.04 | parameter-missing-const | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-DCL00 | |
CodeSonar | 8.1p0 | LANG.CAST.PC.CRCQ LANG.TYPE.VCBC LANG.STRUCT.RPNTC | Cast removes const qualifier Variable Could Be const Returned Pointer Not Treated as const |
Compass/ROSE | |||
1.2 | CC2.DCL00 | Partially implemented | |
Helix QAC | 2024.3 | C3204, C3227, C3232, C3673, C3677 | |
LDRA tool suite | 9.7.1 | 78 D | Fully implemented |
Parasoft C/C++test | 2023.1 | CERT_C-DCL00-a | Declare local variable as const whenever possible |
PC-lint Plus | 1.4 | 953 | Fully supported |
Polyspace Bug Finder | R2024a | CERT C: DCL00-C | Checks for unmodified variable not const-qualified (rule fully covered). |
RuleChecker | 24.04 | parameter-missing-const | Partially checked |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Bibliography
[Dewhurst 2002] | Gotcha #25, "#define Literals" |
[Saks 2000] |
22 Comments
Josh Triplett
What exactly makes it a feature to prevent the programmer from taking the address of a const integer?
Greg Beeley
The most significant case I could think of would be where you have an API function that takes an integer pointer as an argument and then changes that argument. For example:
If the programmer passes a constant integer to our hypothetical function here, not realizing that the function actually modifies msec_to_wait to indicate how many msec's actually elapsed or remain (roughly analogous to the way that select(2) can do for a struct timeval), unexpected behavior will occur. Such a mistake would not be possible with an enum constant or a #define constant - it would force putting the constant in a variable first, and then calling wait_for_event().
Of course, the programmer should use const which will elicit a compiler warning when passed to this function, and I believe there is another recommendation on this site regarding functions that take pointers to values like this, but nevertheless with secure coding it is usual to follow the "belt and suspenders" principle and nip something in the bud at various levels.
Douglas A. Gwyn
I basically disagree with this guideline. The only real value I see in this practice is that a debugger will show the identifier rather than the numeric value. That has nothing to do with code safety. Using #define avoids most of the problems listed for the other techniques. I think this guideline originates in a C++ bias against the preprocessor.
Thomas Plum
I think the text is ok. It's only a recommendation. But Doug's comment suggests adding a further clarification, such as:
In a C project, constant values are often provided by #define in preference to const object data; indeed, this is the only way to provide a named compile-time constant in C.
David Svoboda
I added a paragraph discussing macros, as well as enumeration constants, as alternatives to const variabbles, along with a link to DCL06, which discusses the three strategies in detail.
Robert Seacord (Manager)
I'm not sure I buy this:
Won't the compiler generate diagnostics if the program tries to change an rvalue or literal as well? Suggest we deleted these two sentences.
Hallvard Furuseth
A strage rule, and a somewhat confused one. The intro
does not seem to say the same as the meat of the rule either.
Objects of any type and complexity can be immutable.
The intro says to constify everything we can, of any type.
This leads to serious "const poisoning" - it requires
functions and variables who will receive pointers to these
objects to be constified too, and then functions/variables
using these functions, and so on. Sooner or later you'll
likely have to cast away a lost of consts somewhere.
Spurious consts are less painful in C++, where you have
'const_cast', 'mutable' and much more ability to play
with types.
Anyway, the meat of the suggestion seems to be something
else: "use enum/const rather than #define". That can
have its benefits at times, other times it would be
limiting or costly. Personally I like enums for small
const ints (for debuggers like you mention), but
their limited both in range and what type I can make them.
No such thing as "unsigned enum {}" for example.
expressions of integer constants and enums can also be
expected to evaluated at compile time, but one can
not expect the same of a const variable. That can
make a noticeable difference at times. Sometimes it
also allows for complex constant expressions which would
be truly ridiculous pessimizations if the compiler
did not optimize them.
David Svoboda
In general it is always easier to use 'const' in new programs, than to try to retrofit it into old programs. Both C and C++ allow you to override constness, with C++ being a bit more mature in this regard (having acquired the const keyword first).
The rule can be summed up as:
Method
Evaluated at
Memory Allocated
Viewable by Debuggers
Types
Enumerations
compile time
no
yes
only
int
const
variablescompile time
yes
yes
any type
Macros
preprocessor
no
no
typeless
Hallvard Furuseth
No. In C, "const" variables need not be evaluated at compile time. In the abstract machine they are not, but the compiler can optimize that. And optimization or no, C programs may not use "const" variables as constant expressions (e.g. for array indexes). So:
gcc-3.4.6 i386/linux evaluates 'a' in foo() at runtime without -O and at compile time with -O.
The array declaration is valid in C++, but en error in C (with or without optimization).
Memory is allocated at startup in the abstract machine, but can be optimized away. BTW, I suggest avoiding the word "allocated" in this sense so it won't be confused with malloc & co.
And be careful with the type of "enumerations" - do you mean the type of the declared "enum" (which is compatible with _some_ integer type) or the enumeration constants (which have type int)?
It is indeed easier to use const in a new program than insert it in an old one, but how easy or how good idea it is still depends on the program's structure, its data structures, what external APIs it'll use, etc.
Abhijit Rao
Few compilers in the embedded world have a build option to specify that the smallest type possible be used for Enum. If this option is turned on then 1 byte can be used to store the enums, if this flag is not set then int is used.
These compilers also let the programmer specify the "signedness" for the enum type - usualy by default it is unsigned.
Also there are variants on how to specify the type - for example there is "#pragma enumsalwaysint" and then there -fshort-enums
A programmer has to be aware of these quirks especially when porting code. So the size of the enum type shoudl be best classifed as compiler implementation defined, and the optimization flags.
C99 specifies that the underlying type for enum is int; C++ specifies it as implementation defined.
Alex Volkovitsky
Does this part of both NCE/CS violate FLP03-C. Detect and handle floating point errors:
David Svoboda
Yes.
Masaki Kubo
What do you mean by "correctness of applications"? The definition of correctness seems to be not so obvious.
David Svoboda
Here we are referring to 'software correctness', IOW software that is bug-free; runs as designed. Our rules are about security, not correctness. But the good news is that security and correctness often go hand-in-hand. Violating this rule has more dire consequences for correctness than for security, but security is affected enough to warrant its presence in this standard.
whyglinux
>> The value
1 << 16
could be anint
or along
depending on the platform.The type of the expression (
1 << 16
) is determined by its integer-promoted left operand, which has type of int.David Svoboda
Took out the offending NCCE/CCE. It's not legit C anyway, as you can't define two
f
functions that take different argument types. The example lives on, however, in VOID DCL00-CPP. Const-qualify immutable objects.Don Bockenfeld
What is the point of the exception (allowing inclusion guard macros)? It is meaningless to refer to const-qualifying a macro value.
Hmm... that suggests a new preprocessor recommendation: don't use #undef followed by #define to alter the value of a macro unless it is done at the top of the source file before the macro has been invoked.
Robert Seacord
Looks like that was left over from an earlier version of the recommendation; I'm removing it.
Mohan Dhanawade
Better noncompliant example here would be, ignoring const in below crc_table definition
dev
Nice page, thanks.
BTW, could you please shed some light on const * <variable type> and <variable type> * const?
Thanks
Aaron Ballman
Some example code to help:
var1 is a mutable pointer to a constant foo.
var2 is a mutable pointer to a constant foo.
var3 is a constant pointer to a mutable foo.
var4 is a constant pointer to a constant foo.
If the pointer is const, then it means the pointer cannot be manipulated (think ++ or --). If the object is const, then the object being pointed to cannot be manipulated. Does that clear things up?
Don Bockenfeld
If you're going to use const to declare a fixed constant then you should go all the way and use static const:
static const float pi = 3.14159f;
Advantages include: