1. How to define an integer constant in C?
This is a tricky issue with a number of options available in C. They include:
3. Explicit use of literal
4. Use of preprocessor #define
5. Use of enum
6. Use of constant int without or with address references
The following table shows a comparison for the above options in terms of CPP, CC and GDB:
Constant Option C Preprocessor C Compiler C Debugger
Integer Literal • Does not see any • Sees a Literal • No Debugger Symbol
Literal or Symbol • Replaces in Expressions Entry
(10) • No Memory Binding
Define Constant • Sees a Symbol • Sees a Literal • No Debugger Symbol
• Replaces Textually • Replaces in Expressions Entry
#define TEN 10 • No Memory Binding
Enum Literal • Does not see any • Sees a Symbol • Debugger Symbol with
Literal or Symbol • Symbol is Constant Constant Value
enum { TEN = 10 }; • Replaces in Expressions • Address Operation on
• No Memory Binding Symbol an Error
Created; and is Not
allowed
Constant Integer • Does not see any • Sees a Symbol • Debugger Symbol with
Literal or Symbol • Symbol is Constant Constant Value
const int TEN = 10; • Replaces in Expressions • Address Operation on
• No Memory Binding Symbol an Error
Created; but is allowed
Constant Integer • Does not see any • Sees a Symbol • Debugger Symbol with
(Address Used) Literal or Symbol • Symbol is Constant Constant Value
• Replaces in Expressions • Address Operation on
const int TEN = 10; for direct use; indirect Symbol allowed
&TEN; use may not be replaced
• Memory Binding Created
in Constant Segment
Next, we illustrate the above with a code below:
#define u 1 // u: Symbol - No. Memory - No
enum { v = 1 }; // v: Symbol - No. Memory - No; Disallowed
const int w = 1; // w: Symbol - Yes. Memory - No; Allowed
const int x = 1; // x: Symbol - Yes. Memory - Yes (Const Segment)
const int *p = &x; // p: Symbol - Yes. Memory - Yes (Data / Static Segment)
void main()
{
// LITERAL
int a[1];
a[0] = 1;
// DEFINE CONSTANT
// 'u' is replaced by CPP
// Expression '&u' is illegal in CPP
// Symbol 'u' is undefined in Debugger
int b[u];
b[0] = u;
// ENUM LITERAL
// 'v' is replaced by CPP
// Expression '&v' is illegal in CPP
// Symbol 'v' is defined in Debugger
int c[v];
2. c[0] = v;
// CONST INT
// 'w' is replaced by CPP
// Expression '&w' is legal in CPP
// Symbol 'w' is defined in Debugger
// Expression '&w' is undefined in Debugger
int d[w];
d[0] = w;
// CONST INT - ADDRESS TAKEN
// 'x' is replaced by CPP
// Expression '&x' is legal in CPP
// Symbol 'x' is defined in Debugger
// Expression '&x' is defined in Debugger
// Reference to 'x' may not be optimized
int e[x];
e[0] = *p;
return;
}
First let us take a look into the CPP output to understand what is actually ‘seen’ by the C
Compiler. The large number of blank lines is due to omitted comment lines from the source.
#line 1 "d:personal_transparent programming_projectsdefine
constantsdefine constantsmain.cxx"
enum { v = 1 };
const int w = 1;
const int x = 1;
const int *p = &x;
void main()
{
int a[1];
a[0] = 1;
int b[1];
b[0] = 1;
int c[v];
c[0] = v;
int d[w];
d[0] = w;
int e[x];
e[0] = *p;
return;
}
Note that the #define symbol ‘u’ has completely disappeared from the source.
Next let us take a look at the Debug Assembly for the code. This tells us about:
7. Constant folding / propagation
8. The lack of optimizations and
9. Memory binding, wherever applicable. These have been highlighted
; Listing generated by Microsoft (R) Optimizing Compiler Version 14.00.50727.42
TITLE d:Personal_Transparent Programming_ProjectsDefine
ConstantsDefine ConstantsMain.cxx
.686P
.XMM
include listing.inc
.model flat
4. ; 31 :
; 32 : // CONST INT
; 33 : // 'w' is replaced by CPP
; 34 : // Expression '&w' is legal in CPP
; 35 : // Symbol 'w' is defined in Debugger
; 36 : // Expression '&w' is undefined in Debugger
; 37 : int d[w];
; 38 : d[0] = w;
mov DWORD PTR _d$[ebp], 1
; 39 :
; 40 :
; 41 : // CONST INT - ADDRESS TAKEN
; 42 : // 'x' is replaced by CPP
; 43 : // Expression '&x' is legal in CPP
; 44 : // Symbol 'x' is defined in Debugger
; 45 : // Expression '&x' is defined in Debugger
; 46 : // Reference to 'x' may not be optimized
; 47 : int e[x];
; 48 : e[0] = *p;
mov eax, DWORD PTR ?p@@3PBHB ; p
mov ecx, DWORD PTR [eax]
mov DWORD PTR _e$[ebp], ecx
; 49 :
; 50 : // Test(a[0], b[0], c[0], d[0], e[0]);
; 51 :
; 52 : return;
; 53 : }
xor eax, eax
push edx
mov ecx, ebp
push eax
lea edx, DWORD PTR $LN9@main
call @_RTC_CheckStackVars@8
pop eax
pop edx
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret 0
npad 2
$LN9@main:
DD 5
DD $LN8@main
$LN8@main:
DD -8 ; fffffff8H
DD 4
DD $LN3@main
DD -20 ; ffffffecH
DD 4
DD $LN4@main
DD -32 ; ffffffe0H
DD 4
DD $LN5@main
DD -44 ; ffffffd4H
DD 4
DD $LN6@main
DD -56 ; ffffffc8H
DD 4
DD $LN7@main
$LN7@main:
DB 101 ; 00000065H
5. DB 0
$LN6@main:
DB 100 ; 00000064H
DB 0
$LN5@main:
DB 99 ; 00000063H
DB 0
$LN4@main:
DB 98 ; 00000062H
DB 0
$LN3@main:
DB 97 ; 00000061H
DB 0
_main ENDP
_TEXT ENDS
END
Please note that unless the address is taken the const int option has all the advantages of the
other schemes and gives rise to the same code (as in other cases). In addition, it can make the
constant symbol visible at the debugger. Interestingly, if a[0], b[0] etc are used somewhere, then
the above codes for their initialization may also get optimized out (unless they are reassigned
some other value at some other place). The same may apply to initialization via *p provided p is
not reassigned in the meanwhile.
Moral: Use const int and do not take its address.