C Language – Variable Data Types
1. C Data Types
signedunsignedshortlongchar
intfloatdoubleautoregister
staticexternconstvolatile
1.1. Integer Sign Modifiers
These are generally used in combination with integer types such as char or int.
- They can also be used alone, in which case they are interpreted as having an implied
int.
signed
unsigned
signed and unsigned are keywords that determine whether an integer type can represent negative values.
signeduses a sign bit to represent both negative and positive values.unsigneddoes not represent negative values and only represents values greater than or equal to 0.
Even with the same 1 byte (8 bits), the range of values differs depending on whether it is signed or unsigned.
1.2. Integer Size Modifiers
These are generally used in combination with the integer type int.
- They can also be used alone, in which case they are interpreted as having an implied
int.
short
long
short
shortis a modifier used to reduce the size of an integer type.
long
longis a modifier used to increase the size of an integer type.- It can represent a wider range of integers than
int, but on 32-bit systems,intandlong intmay have the same size.
1.3. Fundamental Types
These are the basic built-in data types in C used to represent integer and floating-point values.
1.3.1. Integer Types
Integer types represent whole numbers without a fractional part. Although char is used to store characters, it is classified as an integer type.
char
int
| Type | Bits | Hex Range | Value Range (Decimal) |
|---|---|---|---|
| (signed) char | 8bit | 0x80 ~ 0x7f | -128 ~ 127 |
| unsigned char | 8bit | 0x00 ~ 0xff | 0 ~ 255 |
| short (int) | 16bit | 0x8000 ~ 0x7fff | -32,768 ~ 32,767 |
| unsigned short (int) | 16bit | 0x0000 ~ 0xffff | 0 ~ 65,535 |
| (signed) int | 32bit | 0x80000000 ~ 0x7fffffff | -2,147,483,648 ~ 2,147,483,647 |
| unsigned int | 32bit | 0x00000000 ~ 0xffffffff | 0 ~ 4,294,967,295 |
| long (int) | 32bit* | 0x80000000 ~ 0x7fffffff | -2,147,483,648 ~ 2,147,483,647 |
| unsigned long (int) | 32bit* | 0x00000000 ~ 0xffffffff | 0 ~ 4,294,967,295 |
- In general, the most significant bit (MSB) is interpreted as the sign bit, and two’s complement representation is used so that both positive and negative arithmetic can be handled with a single adder, simplifying hardware and keeping operations consistent.
※ Under C90, integer types are signed by default. Therefore, even if signed is not explicitly written, the compiler interprets it as signed by default, so it is generally omitted. Since short and long are also modifiers of int, int is treated as the default and may be omitted as well.
※ Since C99, long long int has been introduced, making 64-bit integer representation possible.
| Type | Bits | Hex Range | Value Range (Decimal) |
|---|---|---|---|
| (signed) long long int | 64bit | 0x8000000000000000 ~ 0x7fffffffffffffff |
-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
| unsigned long long int | 64bit | 0x0000000000000000 ~ 0xffffffffffffffff |
0 ~ 18,446,744,073,709,551,615 |
1.3.2. Floating-Point Types
Floating-point types represent real numbers with a fractional part and store approximate values using floating-point representation.
float
double
| Type | Storage Size | Value Range (Decimal) | Precision (Decimal) |
|---|---|---|---|
| float | 4 bytes | 1.2E-38 ~ 3.4E+38 | 6 decimal places |
| double | 8 bytes | 2.2E-308 ~ 1.8E+308 | 15 decimal places |
| long double | 10 bytes* | 3.4E-4932 ~ 1.1E+4932 | 19 decimal places |
floatanddoublerepresent real numbers using a sign, exponent, and mantissa.- The exponent determines the representable range, while the mantissa determines precision.
floatis lighter and faster but has limited range and precision, whereasdoubleprovides a wider range and higher precision.
※ The storage size and range of long double may vary depending on the platform and compiler.
Floating-Point Representation
Floating-point representation stores real numbers by allowing the decimal point to move freely, and instead of storing the exact decimal value, it stores an approximation using the nearest binary value.
#include <math.h>
double a = 0.1;
double b = 0.2;
if ((a + b) != 0.3) {
/* condition is true due to floating-point precision error */
}
if (fabs((a + b) - 0.3) < 1e-9) {
/* treated as equal */
}
- The computer stores
0.1and0.2not as exact values, but as the nearest binary approximations. - As a result, the actual internal value of
a + bbecomes something slightly off from the exact value, such as0.30000000000000004. - Therefore, even though it looks equal to
0.3to a human, the program does not treat it as exactly the same.
1.4. Storage Class Specifiers
A storage class specifier is a keyword that determines the lifetime and scope of a variable.
auto
register
static
extern
1.4.1. auto
- It is the default storage class for local variables declared inside a block.
- Even if not specified explicitly, all local variables are
autoby default, so it is generally not written explicitly. - Since C99,
autohas effectively become a meaningless keyword.
auto int x; /* same as int x; */
1.4.2. register
- It is a specifier that requests that a variable be stored in a CPU register.
- It is only a hint intended to improve access speed, and whether it is actually stored in a register is decided by the compiler.
- You cannot use the address-of operator (&).
register int i;
※ Since C11, most compilers perform optimization automatically, so the register keyword is effectively meaningless.
1.4.3. static
1.4.3.1. Local Variables
- Its lifetime lasts for the entire program, while its scope remains inside the block.
- Its value is preserved even after the function call ends.
void func() {
static int count = 0;
count++;
}
1.4.3.2. Global Variables
- Makes it accessible only within the file.
- It cannot be referenced from other source files.
static int global;
1.4.4. extern
- Used to refer to a global variable defined in another file.
- It does not allocate actual storage; it only provides a declaration.
extern int shared;
1.5. Type Qualifiers
Type qualifiers are keywords that restrict the meaning and behavior of a variable. const means the value cannot be modified, and volatile indicates that it may be changed externally.
const
volatile
1.5.1. const
constis a qualifier that restricts a variable so that its value cannot be changed.- Once declared, its value cannot be modified through assignment.
const int max = 10;
/* max = 20; // compilation error */
1.5.2. volatile
volatileindicates that a variable’s value may be changed externally, so the program should always use the latest value from memory.- It tells the compiler not to optimize the variable away and to always read the value again from memory.
volatile const int reg;
It is used for variables that may be changed by hardware registers or interrupts.
※ volatile does not guarantee multithreaded synchronization and does not replace atomic operations or mutexes.
2. Printing C Data Types
2.1. Example
The following example prints the sizes of various data types.
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#define SIZEOF_BITS(type) (sizeof(type) * 8)
int main(void)
{
/* Character types */
printf("%-19s : %zu byte (%zu bit)\n", "char", sizeof(char), SIZEOF_BITS(char));
printf("%-19s : %zu byte (%zu bit)\n", "signed char", sizeof(signed char), SIZEOF_BITS(signed char));
printf("%-19s : %zu byte (%zu bit)\n", "unsigned char", sizeof(unsigned char), SIZEOF_BITS(unsigned char));
printf("\n");
/* Boolean type */
printf("%-19s : %zu byte (%zu bit)\n", "bool", sizeof(bool), SIZEOF_BITS(bool));
printf("\n");
/* Integer types */
printf("%-19s : %zu byte (%zu bit)\n", "short", sizeof(short), SIZEOF_BITS(short));
printf("%-19s : %zu byte (%zu bit)\n", "unsigned short", sizeof(unsigned short), SIZEOF_BITS(unsigned short));
printf("%-19s : %zu byte (%zu bit)\n", "int", sizeof(int), SIZEOF_BITS(int));
printf("%-19s : %zu byte (%zu bit)\n", "unsigned int", sizeof(unsigned int), SIZEOF_BITS(unsigned int));
printf("%-19s : %zu byte (%zu bit)\n", "long", sizeof(long), SIZEOF_BITS(long));
printf("%-19s : %zu byte (%zu bit)\n", "unsigned long", sizeof(unsigned long), SIZEOF_BITS(unsigned long));
printf("%-19s : %zu byte (%zu bit)\n", "long long", sizeof(long long), SIZEOF_BITS(long long));
printf("%-19s : %zu byte (%zu bit)\n", "unsigned long long", sizeof(unsigned long long), SIZEOF_BITS(unsigned long long));
printf("\n");
/* Floating-point types */
printf("%-19s : %zu byte (%zu bit)\n", "float", sizeof(float), SIZEOF_BITS(float));
printf("%-19s : %zu byte (%zu bit)\n", "double", sizeof(double), SIZEOF_BITS(double));
printf("%-19s : %zu byte (%zu bit)\n", "long double", sizeof(long double), SIZEOF_BITS(long double));
printf("\n");
/* Fixed-width integer types from stdint.h */
printf("%-19s : %zu byte (%zu bit)\n", "int8_t", sizeof(int8_t), SIZEOF_BITS(int8_t));
printf("%-19s : %zu byte (%zu bit)\n", "uint8_t", sizeof(uint8_t), SIZEOF_BITS(uint8_t));
printf("%-19s : %zu byte (%zu bit)\n", "int16_t", sizeof(int16_t), SIZEOF_BITS(int16_t));
printf("%-19s : %zu byte (%zu bit)\n", "uint16_t", sizeof(uint16_t), SIZEOF_BITS(uint16_t));
printf("%-19s : %zu byte (%zu bit)\n", "int32_t", sizeof(int32_t), SIZEOF_BITS(int32_t));
printf("%-19s : %zu byte (%zu bit)\n", "uint32_t", sizeof(uint32_t), SIZEOF_BITS(uint32_t));
printf("%-19s : %zu byte (%zu bit)\n", "int64_t", sizeof(int64_t), SIZEOF_BITS(int64_t));
printf("%-19s : %zu byte (%zu bit)\n", "uint64_t", sizeof(uint64_t), SIZEOF_BITS(uint64_t));
printf("\n");
return 0;
}
Ubuntu 24.04(x86_64)
char : 1 byte (8 bit)
signed char : 1 byte (8 bit)
unsigned char : 1 byte (8 bit)
bool : 1 byte (8 bit)
short : 2 byte (16 bit)
unsigned short : 2 byte (16 bit)
int : 4 byte (32 bit)
unsigned int : 4 byte (32 bit)
long : 8 byte (64 bit)
unsigned long : 8 byte (64 bit)
long long : 8 byte (64 bit)
unsigned long long : 8 byte (64 bit)
float : 4 byte (32 bit)
double : 8 byte (64 bit)
long double : 16 byte (128 bit)
int8_t : 1 byte (8 bit)
uint8_t : 1 byte (8 bit)
int16_t : 2 byte (16 bit)
uint16_t : 2 byte (16 bit)
int32_t : 4 byte (32 bit)
uint32_t : 4 byte (32 bit)
int64_t : 8 byte (64 bit)
uint64_t : 8 byte (64 bit)
※ The size of a data type may differ depending on the compiler implementation, OS, and architecture. → Use Fixed-width Integer Types
2.2. Void Type
voidis a special type that means "no value."
It is used when a function has no parameters or no return value.
stdbool.h
void init(void); /* no parameters and no return value */
※ In pointers, void means a generic pointer (void *) whose data type is unspecified.
2.3. Boolean type
Based on C99
<stdbool.h>definesbool- It also provides the
true/falsemacros
stdbool.h
#define bool _Bool
#define true 1
#define false 0
- Its size is 1 byte, and internally it is an integer
_Boolis actually anunsigned charinteger type- Any nonzero value is true
- If an integer value is not 0, it is interpreted as
true; if it is 0, it is interpreted asfalse. - You can assign a nonzero integer to
bool, but it is automatically normalized to 0 or 1.
2.4. Fixed-width Types
Used to guarantee the same integer size regardless of platform or compiler.
stdint.h
typedef __int8_t int8_t;
typedef __int16_t int16_t;
typedef __int32_t int32_t;
typedef __int64_t int64_t;
typedef __uint8_t uint8_t;
typedef __uint16_t uint16_t;
typedef __uint32_t uint32_t;
typedef __uint64_t uint64_t;
Ubuntu 24.04(x86_64)
typedef signed char __int8_t;
typedef short __int16_t;
typedef int __int32_t;
typedef long long __int64_t;
typedef unsigned char __uint8_t;
typedef unsigned short __uint16_t;
typedef unsigned int __uint32_t;
typedef unsigned long long __uint64_t;
- Fixed-width Integer Types are used to keep the bit width of integer types exactly fixed.
- They always guarantee the same size and range even across different platforms and compilers.
- For this reason, they are used in code where portability and binary compatibility are important.