Introduction

C is a small, statically typed, compiled language in which the programmer works close to the machine: every object has a type that fixes how many bytes it occupies and how those bytes are interpreted. Predicting what a C program prints depends, nearly every time, on two things introduced here: how a C program is put together, and exactly what values each basic type can hold.

This note fixes the conventions used by every C note in this subject.

  • Standard. C11 (ISO/IEC 9899:2011), hosted implementation (a normal operating system with the full standard library).
  • Data model for examples. Unless a question states otherwise, examples assume LP64, the model of 64-bit Linux and macOS with gcc or clang: char 1 byte, short 2, int 4, long 8, long long 8, pointers 8, and two's complement integers. The other common model, ILP32 (32-bit systems), differs only in long and pointers being 4 bytes. Windows 64-bit uses LLP64 (long 4 bytes, pointers 8).
  • GATE convention. When a question gives sizes ("assume int is 4 bytes"), use them; they override everything here.

Prerequisites

Core concepts

Structure of a C program

A C program is one or more translation units (a source file after preprocessing), compiled separately and then linked. A minimal hosted program:

C
#include <stdio.h>          /* preprocessor directive: textual inclusion */
#define N 3                 /* macro: replaced before compilation */

int square(int x);          /* declaration (prototype) */

int main(void) {            /* execution starts here */
    int total = 0;          /* definition with initialisation */
    for (int i = 1; i <= N; i++)
        total += square(i);
    printf("sum of squares = %d\n", total);
    return 0;               /* 0 means success to the host */
}

int square(int x) { return x * x; }   /* definition */

Output: sum of squares = 14.

The pieces, in the order the toolchain sees them:

  1. Preprocessing. Lines starting with # are handled first: #include pastes a header, #define sets up text replacement. See The C Preprocessor.
  2. Compilation. Each translation unit becomes an object file. A function must be declared before it is called; the prototype int square(int x); lets main call square, which is defined later.
  3. Linking. Object files and the library are joined; every function or object that is used must be defined exactly once in the whole program.
  4. Execution starts at main. In a hosted implementation the two portable forms are int main(void) and int main(int argc, char *argv[]). Reaching the closing brace of main is equivalent to return 0; (C99 onward), but only for main.

Declaration versus definition. A declaration announces a name and its type. A definition also creates the object (allocates storage) or supplies the function body. extern int count; is a declaration only; int count = 5; at file scope is a definition. The details are in Scope, Storage Classes and Linkage.

Tokens: identifiers, keywords and constants

  • Identifiers consist of letters, digits and underscores and must not start with a digit. They are case-sensitive: Count and count are different. Names beginning with an underscore followed by an uppercase letter or a second underscore are reserved for the implementation.
  • Keywords are reserved words that cannot be identifiers. C11 has 44 of them, from auto and break to _Static_assert and _Thread_local. Note that main, printf and NULL are not keywords.
  • Comments /* ... */ do not nest; // comments run to the end of the line (C99 onward). Comments are replaced by a single space before preprocessing directives are executed.

The basic types

C has integer types, floating types and void. Their sizes are not fixed by the standard; only minimum ranges and an ordering are guaranteed.

TypeMinimum range guaranteed by C11 (limits.h)Typical size, LP64
signed char−127-127 to 1271271 byte
unsigned char00 to 2552551 byte
short−32767-32767 to 32767327672 bytes
int−32767-32767 to 32767327674 bytes
long−2147483647-2147483647 to 214748364721474836478 bytes
long long−(263−1)-(2^{63}-1) to 263−12^{63}-18 bytes
floatat least 6 significant decimal digits (FLT_DIG)4 bytes
doubleat least 10 significant decimal digits (DBL_DIG)8 bytes

Guarantees that hold on every conforming implementation:

  • sizeof(char) == 1 by definition. A "byte" in C is CHAR_BIT bits, at least 8.
  • range(signed char)⊆range(short)⊆range(int)⊆range(long)⊆range(long long)\text{range(signed char)} \subseteq \text{range(short)} \subseteq \text{range(int)} \subseteq \text{range(long)} \subseteq \text{range(long long)}.
  • Each signed type has an unsigned counterpart of the same size.
  • Whether plain char is signed or unsigned is implementation-defined. On x86 gcc it is signed (range −128-128 to 127127); on ARM Linux it is unsigned. char, signed char and unsigned char are three distinct types.

The minimum ranges look one short on the negative side (−127-127, not −128-128) because C11 still permits sign-and-magnitude and ones' complement representations. Every real machine uses two's complement, and C23 makes it mandatory.

Integer type sizes and ranges under LP64 and ILP32
Figure 1. Sizes and two's complement ranges of the C integer types under the LP64 and ILP32 data models, with the C11 guaranteed minimums.

Ranges from the bit width

For an nn-bit integer type:

unsigned: 0 to 2n−1,two’s complement signed: −2n−1 to 2n−1−1.\text{unsigned: } 0 \text{ to } 2^{n}-1, \qquad \text{two's complement signed: } -2^{n-1} \text{ to } 2^{n-1}-1 .

Here n=8×n = 8 \times sizeof(type) when CHAR_BIT is 8. With n=8n = 8: unsigned 0 to 255, signed −128-128 to 127. With n=32n = 32: unsigned 0 to 4294967295, signed −2147483648-2147483648 to 2147483647. The signed range is asymmetric: there is one more negative value than positive.

Constants and their types

Every constant has a type, and the type decides the arithmetic.

  • Integer constants. Decimal 42, octal 052 (leading zero), hexadecimal 0x2A. All three are the value 42. 08 is a compile error, because 8 is not an octal digit. A decimal constant without suffix gets the first of int, long, long long that can hold it; octal and hexadecimal constants may also become unsigned types. Suffixes: U (unsigned), L (long), LL (long long), combinable as UL, ULL.
  • Floating constants. 1.5, 2e-3, .5 have type double. Suffix f gives float, L gives long double. So sizeof 1.5 is 8 but sizeof 1.5f is 4.
  • Character constants. 'A' has type int in C (not char, unlike C++), so sizeof('a') == sizeof(int), which is 4 under LP64. Its value is the character's code: 65 in ASCII. Escape sequences include '\n', '\t', '\0' (value 0), '\\', '\'', octal '\101' and hex '\x41' (both 65).
  • String literals. "abc" is an array of 4 char (the 3 characters plus the terminating '\0'), so sizeof "abc" is 4. Modifying a string literal is undefined behaviour. See Strings.

Character arithmetic

Because characters are small integers, arithmetic on them is ordinary integer arithmetic. In ASCII the digits '0' to '9' are consecutive (C guarantees this for digits in every character set), so '9' - '0' is 9 and '0' + 7 is 55, the code of '7'. The letters are consecutive in ASCII but this is not guaranteed by C, so 'A' + 2 == 'C' is true under ASCII only.

Integer division and remainder

Since C99, integer division truncates toward zero, and the remainder satisfies

a=(a/b)⋅b+a % b(b≠0),a = (a / b) \cdot b + a \,\%\, b \qquad (b \ne 0),

so the remainder takes the sign of the dividend aa. Examples: 7/2 is 3, -7/2 is −3-3, 7/-2 is −3-3; 7%3 is 1, -7%3 is −1-1, 7%-3 is 1. Division or remainder by zero is undefined behaviour, and so is INT_MIN / -1 (the result does not fit). The operands of % must be integers.

Unsigned wrap-around versus signed overflow

  • Unsigned arithmetic is performed modulo 2n2^n. unsigned int u = 0; u = u - 1; gives 4294967295 for a 32-bit unsigned int. This is well defined.
  • Signed overflow (a result outside the type's range, such as INT_MAX + 1) is undefined behaviour. It is not "wrap to negative"; the compiler may assume it never happens. See Undefined, Unspecified and Implementation-Defined Behaviour.
  • Integer-to-integer conversion. Conversion to an unsigned type reduces modulo one more than that type's maximum value; with an 8-bit unsigned char, 255 plus 1 stores 0. An unrepresentable integer converted to a signed integer type has an implementation-defined result or raises an implementation-defined signal. This is not the floating-to-integer rule: finite floating values truncate toward zero, and an unrepresentable integral part makes the conversion undefined. See Type Conversions.

Floating-point values

On essentially every current system float and double are IEEE 754 binary32 and binary64. Most decimal fractions, such as 0.1, are not exactly representable, so the stored value is the nearest binary fraction. Consequences:

  • float f = 0.1f; then f == 0.1 is false (0): f is converted to double for the comparison, but the float approximation of 0.1 differs from the double approximation. f == 0.1f is true.
  • For inexact computations where approximate numerical agreement is intended, choose a suitable absolute/relative tolerance rather than blindly testing equality; exact equality is valid when the specification calls for it.
  • Mixed expressions convert to the wider floating type: 5 / 2 * 2.0 is 2 * 2.0, which is 4.0, because 5 / 2 is integer division done first. 5 / 2.0 * 2 is 5.0.

Representation details belong to Floating-Point Representation.

sizeof

sizeof is an operator, not a function. It yields a value of type size_t (an unsigned integer type) and is normally evaluated at compile time. Its operand is not evaluated (the one exception is a variable length array): after int i = 5; size_t s = sizeof(i++);, i is still 5 and s is 4. Parentheses are required only for a type name: sizeof(int) but sizeof x. Print a size_t with %zu.

printf and scanf format specifiers

SpecifierType expected by printf
%d, %iint
%u, %x, %ounsigned int (decimal, hex, octal)
%ld, %lldlong, long long
%cint (printed as a character)
%f, %e, %gdouble (a float argument is promoted to double)
%schar * to a null-terminated string
%pvoid *
%zusize_t

A specifier that does not match the type of the argument is undefined behaviour (for example printf("%d", 3.5)). scanf needs addresses: scanf("%d", &x), and %f in scanf means float * while %lf means double *.

Worked examples

All outputs below are for C11 with gcc on x86-64 Linux (LP64).

Example 1 (sizes). printf("%zu %zu %zu %zu", sizeof(int), sizeof(long), sizeof(void*), sizeof('a')); prints 4 8 8 4. Under ILP32 it would print 4 4 4 4.

Example 2 (constants). printf("%d %d %d %d", 010, 0x1F, 'A' + 1, '0' + 7); prints 8 31 66 55. The leading zero makes 010 octal.

Example 3 (division). printf("%d %d %d", -7/2, -7%3, 7%-3); prints -3 -1 1. Check with a=(a/b)b+a%ba = (a/b)b + a\%b: −7=(−3)(2)+(−1)-7 = (-3)(2) + (-1) is the quotient-remainder pair for −7/2-7/2; for −7%3-7\%3, −7=(−2)(3)+(−1)-7 = (-2)(3) + (-1).

Example 4 (wrap-around and floats).

C
unsigned char c = 255; c = c + 1;     /* c becomes 0 */
unsigned int  u = 0;   u = u - 1;     /* u becomes 4294967295 */
float f = 0.1f;
printf("%d %u %d %d\n", c, u, f == 0.1, f == 0.1f);

prints 0 4294967295 0 1.

Example 5 (mixed arithmetic). printf("%.1f %d", 5 / 2 * 2.0, (int)(5 / 2.0 * 2)); prints 4.0 5.

Important properties and observations

  • Sizes are implementation-defined; only minimum ranges and ordering are guaranteed. Always state or read the data model.
  • 'a' is an int in C. sizeof('a') is sizeof(int), not 1.
  • A leading zero makes an integer constant octal; 08 and 09 do not compile.
  • Integer division truncates toward zero; the remainder has the sign of the dividend.
  • Unsigned arithmetic wraps modulo 2n2^n; signed overflow is undefined behaviour.
  • The signedness of plain char is implementation-defined.
  • The operand of sizeof is not evaluated (except for variable length arrays).
  • Floating constants are double unless suffixed with f.

GATE examination perspective

  • Output prediction. Most C questions ask for printed output. Track each variable's type first, then its value; type decides whether / truncates, whether a value wraps and how printf prints it.
  • Stated sizes. If the question says int is 2 bytes, the range is −32768-32768 to 3276732767 (two's complement) and 32767 + 1 in int is overflow.
  • Octal trap. int a = 012; is 10, not 12.
  • Character trap. printf("%d", 'A') prints 65; printf("%c", 65) prints A.
  • Floating comparison trap. float x = 0.1; if (x == 0.1) is false; with 0.5 or 0.25 (exactly representable) it is true.
  • Division trap. 3/2*2 is 2, while 3*2/2 is 3.

Summary

Sources

  1. ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09), Sections 5.1.2.2 (hosted environment, main), 5.2.4.2.1 (sizes of integer types, limits.h), 5.2.4.2.2 (float.h), 6.2.5 (types), 6.4.1 (keywords), 6.4.4 (constants), 6.5.3.4 (sizeof), 6.5.5 (multiplicative operators), 6.3.1.3 (signed and unsigned integer conversions).
  2. B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed., Prentice Hall, 1988: Chapter 1 (a tutorial introduction) and Chapter 2 (types, operators and expressions).

Next steps