Skip to content

C Constants — #define, const, enum, and Literal Suffixes Explained

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C Constants. We cover key concepts, practical examples, and best practices to help you master this topic.

C constants are fixed values that do not change during program execution. They are defined using the const keyword, #define preprocessor directives, enum types, or literal suffixes for type-specific constant values.

Why It Matters

Hardcoding values like 3.14159 or 100 directly in your code creates maintenance problems. If the value changes, you must find and replace every occurrence. Constants give names to values, making code self-documenting and easy to modify. They also help the compiler optimize your code. In systems programming, constants are used for port addresses, buffer sizes, error codes, and configuration parameters.

Real-World Use

The Linux kernel defines thousands of constants for system call numbers, signal types, and device flags. Socket APIs use SOCK_STREAM and SOCK_DGRAM constants instead of magic numbers 1 and 2. Durga Antivirus Pro uses constants for virus signature buffer sizes, scan flags, and error codes.

What You Will Learn

  • The const keyword for read-only variables
  • The #define preprocessor directive for macros
  • The enum type for grouped integer constants
  • Literal suffixes for type-specific values
  • When to use each approach and why

Learning Path

flowchart LR
  A[Variables] --> B[Constants
You are here] B --> C[Operators] C --> D[Control Flow] D --> E[Loops] style B fill:#f90,color:#fff

The const Keyword

The const qualifier makes a variable read-only. Once initialized, its value cannot be changed:

#include <stdio.h>

int main() {
    const double PI = 3.1415926535;
    const int MAX_BUFFER_SIZE = 4096;
    const char NEWLINE = '\n';
    
    printf("PI: %.10f\n", PI);
    printf("Buffer size: %d\n", MAX_BUFFER_SIZE);
    printf("Newline code: %d\n", NEWLINE);
    
    // PI = 3.14;  // Error: cannot modify const variable
    
    return 0;
}

Expected output:

PI: 3.1415926535
Buffer size: 4096
Newline code: 10

const vs Regular Variables

The key difference between const int x = 5 and int x = 5 is that the compiler enforces immutability. Any attempt to modify a const variable produces a compile-time error. The compiler can also optimize const variables more aggressively, potentially placing them in read-only memory.

const Pointers

The const qualifier becomes especially important with pointers:

const int *p;      // Pointer to const int: can modify pointer, not value
int * const p;     // Const pointer to int: can modify value, not pointer
const int * const p; // Const pointer to const int: neither can change

Understanding these distinctions is critical for writing interfaces that promise not to modify data.

The #define Directive

#define is a preprocessor directive that performs text substitution before compilation:

#include <stdio.h>

#define PI 3.14159
#define MAX_STUDENTS 30
#define GREETING "Hello, World!"
#define SQUARE(x) ((x) * (x))

int main() {
    printf("PI: %f\n", PI);
    printf("Max students: %d\n", MAX_STUDENTS);
    printf("%s\n", GREETING);
    printf("Square of 5: %d\n", SQUARE(5));
    printf("Square of 3+2: %d\n", SQUARE(3+2));
    
    return 0;
}

Expected output:

PI: 3.141590
Max students: 30
Hello, World!
Square of 5: 25
Square of 3+2: 25

Why Parentheses Matter in Macros

#define BAD_SQUARE(x) x * x
BAD_SQUARE(3+2)  // Expands to: 3+2 * 3+2 = 3 + 6 + 2 = 11

Without parentheses around the parameter and the entire expression, operator precedence changes the meaning. Always wrap macro parameters and the full expression in parentheses.

#define vs const

Aspect #define const
When evaluated Preprocessing Compilation
Type safety None (text substitution) Full Type Checking
Scope File-wide after definition Block scope
Debugging Cannot be inspected in debugger Visible in debugger
Memory No storage allocated Stored in memory
Pointer support No Yes (const pointers)

For most purposes, prefer const over #define. Use #define for macros that require text substitution (like SQUARE(x)) or conditional compilation guards.

Enum Constants

An enum defines a group of related named integer constants:

#include <stdio.h>

// Define an enumeration
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN };
enum Status { OK = 0, ERROR = -1, TIMEOUT = 1 };
enum Color { RED = 1, GREEN = 2, BLUE = 4, YELLOW = RED | GREEN };

int main() {
    enum Day today = WED;
    enum Status result = OK;
    
    printf("Today: %d\n", today);        // 2 (WED = 2)
    printf("MON: %d\n", MON);            // 0
    printf("SUN: %d\n", SUN);            // 6
    
    printf("Status: %d\n", result);      // 0
    
    if (result == OK) {
        printf("Operation succeeded.\n");
    }
    
    // Enum values without the enum keyword (with typedef)
    // enum Color bg = BLUE;
    // printf("Blue: %d\n", bg);         // 4
    
    return 0;
}

Expected output:

Today: 2
MON: 0
SUN: 6
Status: 0
Operation succeeded.

Enum Rules

  • Values start at 0 by default and increment by 1
  • You can assign specific values to enum constants
  • Multiple names can have the same value
  • Enums are integers -- you can use them wherever int is expected
  • The actual underlying type is int

Literal Suffixes

Literal suffixes specify the exact type of a numeric constant:

#include <stdio.h>

int main() {
    // Integer suffixes
    42          // int
    42U         // unsigned int
    42L         // long
    42UL        // unsigned long
    42LL        // long long
    42ULL       // unsigned long long
    
    // Floating-point suffixes
    3.14        // double
    3.14f       // float
    3.14L       // long double
    
    // Hexadecimal and octal
    0xFF        // 255 in hex
    0777        // 511 in octal
    0b1010      // 10 in binary (C23/GCC extension)
    
    printf("42UL: %lu\n", 42UL);
    printf("0xFF: %d\n", 0xFF);
    printf("0777: %d\n", 0777);
    
    return 0;
}

Expected output:

42UL: 42
0xFF: 255
0777: 511

Using the correct suffix prevents type conversion warnings and ensures the constant has the exact type you expect.

When to Use Each Approach

Situation Best Approach
Single numeric constant const int MAX = 100;
Group of related constants enum Color { RED, GREEN, BLUE };
Type-independent constant #define BUFFER_SIZE 256
Macro with parameters #define MIN(a,b) (((a)<(b))?(a):(b))
String constant const char *MSG = "hello";
Compile-time integer constant enum { MAX = 100 }; (pre-C99)
Constant expression for array size constexpr int N = 100; (C23)

Common Mistakes

1. Using #define Without Type Safety

#define MAX 1000
char arr[MAX];  // Works, but MAX is not a type-checked int

The preprocessor simply replaces MAX with 1000 before the compiler sees it.

2. Macro Side Effects

#define MAX(a,b) ((a) > (b) ? (a) : (b))
int x = 5;
int y = MAX(x++, 10);  // x++ evaluates twice: x becomes 7!

Avoid macros that evaluate arguments more than once.

3. Forgetting the Semicolon After #define

#define MAX 100;  // Wrong! Semicolon becomes part of the constant
int x = MAX;  // Expands to: int x = 100;;

Do not add semicolons to #define directives.

4. Using const for Array Sizes (Pre-C23)

const int SIZE = 10;
int arr[SIZE];  // Error in C89, OK in C99+ with VLA

In C89, array sizes must be compile-time constants. Use #define or enum for array dimensions.

5. Confusing const with constexpr (C23)

const int SIZE = 10;  // Read-only, but not necessarily compile-time
constexpr int SIZE_C = 10;  // Compile-time constant (C23)

In C23, use constexpr when you need a true compile-time constant.

Practice Questions

  1. What is the difference between const int x = 5 and #define x 5? const int creates a typed, scoped variable with memory. #define performs text substitution before compilation without type checking.

  2. What values do enum constants start from? They start from 0 by default and increment by 1 for each subsequent constant.

  3. What suffix makes a literal a float instead of double? The f suffix: 3.14f is a float, 3.14 is a double.

  4. Why should macro parameters be wrapped in parentheses? To prevent operator precedence issues when the macro argument is an expression like 3+2.

  5. Challenge: Write a program that uses enum for traffic light states and switches between them.

Mini Project: Color Constants

Create a program that defines color constants using enum and uses them for formatting:

#include <stdio.h>

enum Color {
    BLACK = 0,
    RED = 1,
    GREEN = 2,
    YELLOW = 3,
    BLUE = 4,
    MAGENTA = 5,
    CYAN = 6,
    WHITE = 7,
    BRIGHT = 8
};

void print_colored(const char *text, enum Color fg) {
    printf("\033[%dm%s\033[0m\n", 30 + fg, text);
}

int main() {
    print_colored("Red text", RED);
    print_colored("Green text", GREEN);
    print_colored("Blue text", BLUE);
    print_colored("Bright white text", WHITE | BRIGHT);
    
    return 0;
}

FAQ

Can I modify a const variable through a pointer?

Technically yes, but the behavior is undefined. The compiler may place const variables in read-only memory, causing a crash on write.

What is the advantage of enum over #define?

Enums are type-checked, scoped, and visible in debuggers. They also allow the compiler to warn about unhandled cases in switch statements.

Can I use const variables as array sizes?

In C99 and later, yes for variable-length arrays on the stack. In C23, constexpr provides true compile-time constants for all contexts.

How do I define a constant string?

Use 'const char *NAME = 'value';' or 'const char NAME[] = 'value';'. Both create read-only string constants.

Should I use ULL suffix for all unsigned long long literals?

Yes, when you need to ensure the literal type. Without the suffix, the compiler may truncate or warn about large values.

What is Next

Now that you understand constants, proceed to Operators in C to learn about arithmetic, relational, logical, bitwise, and assignment operators.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C