CoreTechED

Loading

Learn why character literals are int in C but char in C++. Understand sizeof differences, function overloading, and portability issues between languages.

Character Literals: C vs C++ Type Difference

Type Difference of Character Literals in C and C++

Character literals (e.g., 'V', 'a', '5') have different types in C and C++. In C, a character literal is treated as type int, while in C++, it is treated as type char. This subtle but important difference affects sizeof evaluations, function overloading, type deduction, and portability between the two languages. Understanding this distinction is essential for programmers who work with both C and C++ or migrate code between them.

This tutorial provides an in-depth exploration of the type difference of character literals in C and C++, its implications, practical examples, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What are Character Literals?

A character literal is a single character enclosed in single quotes:

c

'A' // character literal '5' // character literal (digit) '\n' // escape sequence (newline) '\0' // null character

Character literals represent character values and are typically stored in char variables. However, the type of the literal itself differs between C and C++.

Type of Numeric Literals

Before diving into character literals, let’s establish the baseline:

  • Integer literals (e.g., 10) have type int in both C and C++.
  • Floating-point literals (e.g., 10.5) have type double in both C and C++.
  • Therefore, sizeof(10) == sizeof(int) and sizeof(10.5) == sizeof(double) in both languages.

Example (C and C++):

c

#include <stdio.h> int main(void) { printf("sizeof(10) = %zu\n", sizeof(10)); printf("sizeof(int) = %zu\n", sizeof(int)); return 0; }

Output (both C and C++):

text

sizeof(10) = 4 sizeof(int) = 4

The Key Difference: Character Literals

LanguageType of 'V'sizeof('V')sizeof(char)
Cint4 (typically)1
C++char11

In C, character literals are int; in C++, they are char. This difference stems from historical reasons and language design goals.

Historical Context

In early C (K&R C), all character literals were treated as int for simplicity. This allowed functions like getchar() to return an int that could represent any character plus EOF (which is typically -1). When C++ was designed, character literals were changed to char to support function overloading and type safety more intuitively.


Syntax & Core Mechanics

Demonstrating the Difference

In C

c

#include <stdio.h> int main(void) { printf("sizeof('V') = %zu\n", sizeof('V')); printf("sizeof(char) = %zu\n", sizeof(char)); return 0; }

Output (C99/C11):

text

sizeof('V') = 4 sizeof(char) = 1

In C++

cpp

#include <iostream> int main() { std::cout << "sizeof('V') = " << sizeof('V') << std::endl; std::cout << "sizeof(char) = " << sizeof(char) << std::endl; return 0; }

Output (C++):

text

sizeof('V') = 1 sizeof(char) = 1

Type of sizeof Result

The sizeof operator returns a value of type size_t, which is an unsigned integer type defined in <stddef.h> (or <cstddef> in C++). The correct format specifier for size_t in C is %zu (C99 and later).

Common Mistake: Using %d or %lu with sizeof can cause warnings or undefined behavior.

c

#include <stdio.h> int main(void) { printf("%d", sizeof('V')); // Warning: expects int, gets size_t return 0; }

Compiler warning (GCC):

text

warning: format '%d' expects argument of type 'int', but argument 2 has type 'long unsigned int' [-Wformat=]

Why the Difference Matters

Function Overloading in C++

C++ supports function overloading, where multiple functions can have the same name but different parameter types. The type of a character literal determines which overload is called.

cpp

#include <iostream> void foo(char c) { std::cout << "From foo: char" << std::endl; } void foo(int i) { std::cout << "From foo: int" << std::endl; } int main() { foo('V'); // Calls foo(char) in C++ return 0; }

Output (C++):

text

From foo: char

If C++ treated character literals as int like C does, it would call foo(int) instead, which would be less intuitive.

Type Deduction

In C++, auto deduces the type of 'V' as char:

cpp

auto c = 'V'; // c is char in C++

In C (using _Generic in C11), you can see the type difference:

c

#define type_name(x) _Generic((x), \ char: "char", \ int: "int", \ default: "other") printf("%s\n", type_name('V')); // "int" in C

Arithmetic Operations

In C, since character literals are int, they participate in arithmetic as integers:

c

int sum = 'A' + 1; // 'A' is 65, sum is 66

In C++, the same expression also works because char is promoted to int in arithmetic. The result is the same, but the intermediate type differs.


Full Practical Code Example with Explanation

This program demonstrates the type difference of character literals in C and C++ using both languages. It shows sizeof, _Generic (C11), function overloading (C++), and practical implications.

C Program: Character Literal Type

c

/** * char_literal_type_c.c * Demonstrates that character literals are int in C. * Compile with: gcc -std=c11 char_literal_type_c.c -o char_literal_c */ #include <stdio.h> /* C11 _Generic to determine type name */ #define type_name(x) _Generic((x), \ char: "char", \ int: "int", \ unsigned int: "unsigned int", \ long: "long", \ default: "other") int main(void) { /* Character literal */ char c = 'V'; /* Size comparison */ printf("=== Size Comparison ===\n"); printf("sizeof('V') = %zu\n", sizeof('V')); printf("sizeof(char) = %zu\n", sizeof(char)); printf("sizeof(int) = %zu\n", sizeof(int)); /* Type determination using _Generic */ printf("\n=== Type Determination ===\n"); printf("Type of 'V' = %s\n", type_name('V')); printf("Type of c (char variable) = %s\n", type_name(c)); printf("Type of 10 (int literal) = %s\n", type_name(10)); /* Arithmetic: character literal is int */ printf("\n=== Arithmetic ===\n"); int sum = 'A' + 1; /* 'A' is int 65 */ printf("'A' + 1 = %d\n", sum); /* Character literal in expression */ printf("'A' * 2 = %d\n", 'A' * 2); /* Size of character literal in expression */ printf("\n=== Expression Size ===\n"); printf("sizeof('V' + 0) = %zu\n", sizeof('V' + 0)); printf("sizeof(c + 0) = %zu\n", sizeof(c + 0)); /* char promoted to int */ return 0; }

Expected output (C11):

text

=== Size Comparison === sizeof('V') = 4 sizeof(char) = 1 sizeof(int) = 4 === Type Determination === Type of 'V' = int Type of c (char variable) = char Type of 10 (int literal) = int === Arithmetic === 'A' + 1 = 66 'A' * 2 = 130 === Expression Size === sizeof('V' + 0) = 4 sizeof(c + 0) = 4

C++ Program: Character Literal Type

cpp

/** * char_literal_type_cpp.cpp * Demonstrates that character literals are char in C++. * Compile with: g++ -std=c++11 char_literal_type_cpp.cpp -o char_literal_cpp */ #include <iostream> #include <typeinfo> /* Function overloading to demonstrate type difference */ void foo(char c) { std::cout << "foo(char) called" << std::endl; } void foo(int i) { std::cout << "foo(int) called" << std::endl; } int main() { /* Character literal */ char c = 'V'; /* Size comparison */ std::cout << "=== Size Comparison ===" << std::endl; std::cout << "sizeof('V') = " << sizeof('V') << std::endl; std::cout << "sizeof(char) = " << sizeof(char) << std::endl; std::cout << "sizeof(int) = " << sizeof(int) << std::endl; /* Type determination using typeid */ std::cout << "\n=== Type Determination ===" << std::endl; std::cout << "Type of 'V' = " << typeid('V').name() << std::endl; std::cout << "Type of c = " << typeid(c).name() << std::endl; std::cout << "Type of 10 = " << typeid(10).name() << std::endl; /* Function overloading */ std::cout << "\n=== Function Overloading ===" << std::endl; foo('V'); // Should call foo(char) in C++ foo(10); // Calls foo(int) /* Auto type deduction */ std::cout << "\n=== Auto Type Deduction ===" << std::endl; auto c2 = 'W'; std::cout << "Type of c2 (auto from 'W') = " << typeid(c2).name() << std::endl; std::cout << "sizeof(c2) = " << sizeof(c2) << std::endl; return 0; }

Expected output (C++):

text

=== Size Comparison === sizeof('V') = 1 sizeof(char) = 1 sizeof(int) = 4 === Type Determination === Type of 'V' = c Type of c = c Type of 10 = i === Function Overloading === foo(char) called foo(int) called === Auto Type Deduction === Type of c2 (auto from 'W') = c sizeof(c2) = 1

Explanation

  1. C Program:
    • sizeof('V') returns 4, proving character literals are int.
    • _Generic confirms the type of 'V' is int.
    • Arithmetic on 'A' works as int arithmetic.
  2. C++ Program:
    • sizeof('V') returns 1, proving character literals are char.
    • typeid confirms the type of 'V' is char.
    • Function overloading: foo('V') calls foo(char).
    • auto deduces 'W' as char.

Common Pitfalls, Edge Cases & Memory Considerations

1. Using %d for sizeof in C

sizeof returns size_t, not int. Using %d causes undefined behavior or warnings.

c

printf("%d", sizeof('V')); // BUG: wrong format specifier

Solution: Use %zu (C99 and later):

c

printf("%zu", sizeof('V'));

2. Assuming Character Literals are char in C

Many programmers coming from C++ or other languages assume 'A' is char in C. This leads to incorrect assumptions about sizeof and type.

c

// C code if (sizeof('A') == sizeof(char)) { // This is FALSE in C }

Solution: Remember that in C, character literals are int. Use char variables when you need char type.

3. Function Overloading Confusion in C++

If you have overloaded functions for char and int, passing a character literal calls the char version in C++. In C, there is no overloading, so the literal is passed as int.

C++:

cpp

void process(char c); void process(int i); process('V'); // Calls process(char)

C (no overloading):

c

void process_char(char c); void process_int(int i); process_char('V'); // 'V' is int, but converted to char (implicit) process_int('V'); // 'V' is int, no conversion needed

4. Multi-Character Literals

Both C and C++ allow multi-character literals like 'AB', but their type and value are implementation-defined.

c

int x = 'AB'; // Implementation-defined value printf("%d\n", x);

Solution: Avoid multi-character literals; they are not portable.

5. Wide Character Literals

Wide character literals (e.g., L'A') have type wchar_t in both C and C++.

c

#include <wchar.h> wchar_t wc = L'A'; printf("%zu\n", sizeof(L'A')); // Typically 4 (size of wchar_t)

6. UTF-16 and UTF-32 Character Literals

C++11 and C11 introduce:

  • u'A' for char16_t (UTF-16)
  • U'A' for char32_t (UTF-32)

These are unaffected by the C/C++ difference for plain character literals.

7. Arithmetic Promotion

In C++, even though 'A' is char, it is promoted to int in arithmetic expressions (integer promotion).

cpp

auto result = 'A' + 1; // result is int, not char

8. Memory Considerations

  • Character literals do not occupy runtime memory as literals; they are encoded in instructions or data.
  • The type difference affects the size of temporaries in expressions.
  • In C, using character literals in expressions may generate slightly larger code because int operations are used instead of char operations.

9. Portability Between C and C++

Code that relies on sizeof('V') will behave differently when compiled as C vs. C++. This can cause subtle bugs when migrating code.

Solution: Avoid using sizeof on character literals in cross-language code. Use sizeof(char) explicitly if you need the size of a character.

10. _Generic in C11

C11 introduced _Generic for type-based selection. It can distinguish between int and char:

c

#define foo(x) _Generic((x), \ char: foo_char, \ int: foo_int)(x) foo('V'); // Calls foo_int because 'V' is int in C

Summary & Best Practices

The type difference of character literals in C and C++ is a fundamental language distinction. In C, 'V' is int; in C++, 'V' is char. This affects sizeof, function overloading, type deduction, and arithmetic.

Key Takeaways

  • In C: sizeof('V') == sizeof(int) (typically 4), and sizeof(char) is 1.
  • In C++: sizeof('V') == sizeof(char) (1).
  • C uses int for character literals for historical simplicity.
  • C++ uses char to support function overloading and intuitive type behavior.
  • Always use %zu for sizeof results in C.
  • Character literals are promoted to int in arithmetic in both languages.

Best Practices

  1. Use %zu when printing sizeof results in C.
  2. Do not assume sizeof('V') equals sizeof(char) in C.
  3. Use explicit char variables when you need char type in C.
  4. Be aware of function overloading in C++: character literals call char overloads.
  5. Use auto in C++ to correctly deduce character literal types.
  6. Avoid multi-character literals; they are non-portable.
  7. Use wide or UTF literals (L'A', u'A', U'A') when working with specific character encodings.
  8. Test code compiled as both C and C++ if portability is required, especially for sizeof on literals.
  9. Use _Generic in C11 for type-based dispatch if needed.
  10. Document assumptions about character literal types in cross-language codebases.

By understanding this subtle difference, you’ll avoid bugs and write more portable code when working with both C and C++.