Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Edited by sciSpider $version (sch jbop) (X_X)@==(Q_Q)@

...

The C programming language has several mechanisms for 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 are named objects (unlike macro definitions), some debugging tools can show the name of the object. The object also consumes memory.

A const-qualified object allows you to specify the exact type of the constant. For example

Code Block
unsigned const int const buffer_size = 256;

defines buffer_size as a constant whose type is unsigned int.

...

const-qualified objects allow the programmer to take the address of the object.

Code Block
const int const max = 15;
int a[max]; /* invalid declaration outside of a function */
const int const *p;

/* a const-qualified object can have its address taken */
p = &max; 

Wiki Markup
{{const}}\-qualified objects are likely to incur some runtime overhead  \[[Saks 01b|AA. C References#Saks 02]\]. Most C compilers, for example, allocate memory for {{const}}\-qualified objects. {{const}}\-qualified objects declared inside a function body may 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 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 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 const *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:

...

Macros may 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

Consumes Memory

Viewable by Debuggers

Type Checking

Compile-time constant expression

Enumerations

compile time

no

yes

yes

yes

const-qualified

run time

yes

yes

yes

no

Macros

preprocessor

no

no

no

yes

...

Noncompliant Code Example

The meaning of the integer literal 18 is not clear in this example.

Code Block
bgColor#ffcccc
/* ... */
if (age >= 18) {
   /* Take action */
}
else {
  /* Take a different action */
}
/* ... */

Compliant Solution

This compliant solution replaces the integer literal 18 with the symbolic constant ADULT_AGE to clarify the meaning of the code.

Code Block
bgColor#ccccff
enum { ADULT_AGE=18 };
/* ... */
if (age >= ADULT_AGE) {
   /* Take action */
}
else {
  /* Take a different action */
}
/* ... */

...

Noncompliant Code Example

Integer literals are frequently used when referring to array dimensions, as shown in this non-compliant noncompliant code example.

Code Block
bgColor#ffcccc
char buffer[256];
/* ... */
fgets(buffer, 256, stdin);

This use of integer literals can easily result in buffer overflows, if for example, the buffer size is reduced but the integer literal used in the call to fgets() is not.

Compliant Solution (enum)

In this compliant solution, the integer literal is replaced with an enumeration constant (see DCL00-C. Const-qualify immutable objects).

...

Enumeration constants can safely be used anywhere a constant expression is required.

Compliant Solution (sizeof)

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 EXP09-AC. Use sizeof to determine the size of a type or variable).

Code Block
bgColor#ccccff
char buffer[256];
/* ... */
fgets(buffer, sizeof(buffer), stdin);

...

When working with sizeof(), keep in mind ARR01-AC. Do not apply the sizeof operator to a pointer when taking the size of an array.

...

Noncompliant Code Example

In this non-compliant 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
bgColor#ffcccc
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 that may be passed as compile-time arguments.

Code Block
bgColor#ccccff
#ifndef PORTNUMBER     /* might be passed on compile line */
#  define PORTNUMBER 1234
#endif

#ifndef HOSTNAME        /* might be passed on compile line */
#  define HOSTNAME "localhost"
#endif

/* ... */

LDAP *ld = ldap_init(HOSTNAME, PORTNUMBER);
if (ld == NULL) {
  perror("ldap_init");
  return(1);
}

Exceptions

DCL06-EX1: While 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.

...

Note that this example does not check for invalid operations (taking the sqrt() of a negative number). See 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 an array declaration) but not elsewhere (like a loop through an array).

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

DCL06-A C

low

unlikely

medium

P2

L3

Automated Detection

The LDRA tool suite V 7.6.0 is able to can detect violations of this recommendation.

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 (besides a few canonical numbers: -1, 0, 1, 2) that appears in the code anywhere besides being assigned to a variable is a magic number, and should instead be assigned to a const integer, enum, or macro. Likewise any string literal (except "" and individual characters) that appears in the code anywhere besides being assigned to a char* or char[] is a magic string.

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

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.3.2.1, "Lvalues, arrays, and function designators," Section 6.7, "Declarations," Section 6.7.2.2, "Enumeration specifiers," and Section 6.10.3, "Macro replacement"
\[[ISO/IEC PDTR 24772|AA. C References#ISO/IEC PDTR 24772]\] "BRS Leveraging human experience"
\[[Saks 01a|AA. C References#Saks 01]\]
\[[Saks 01b|AA. C References#Saks 02]\]
\[[Saks 02|AA. C References#Saks 02]\]
\[[Summit 05|AA. C References#Summit 05]\] [Question 10.5b|http://c-faq.com/cpp/constvsdefine.html]

...

DCL05-C. Use typedefs to improve code readability      02. Declarations and Initialization (DCL)       DCL07-A. Include the appropriate type information in function declarators Image Added