G27
Self-checkedTier 1 15 min readpds.c-types-operators

Data types, operators and expressions

What a C value really is (a fixed number of bits), how operands are converted before every operation, and the precedence and overflow rules that GATE expression questions are built on.

Why it exists

Mathematics has integers of any size. A C int has exactly 32 bits (on the machines GATE assumes), so there are only 2322^{32} values. Unsigned arithmetic quietly wraps around at the ends, and signed arithmetic that leaves the range is undefined. On top of that, every operation in C happens at a particular type, and C silently converts operands to a common type before computing. Most "what does this program print" questions about expressions are really testing these two facts: the width of the values, and the conversions the compiler inserts.

If you can answer three questions about any expression, you can evaluate it: what is the type of each operand after conversion, what does the operator do at that type, and what happens when the result is stored into a variable of another type.

Prerequisites

None. This is a starting point.

You should be comfortable writing numbers in binary and in two's complement, and you should know what an expression and a statement are. Nothing else from C is assumed.

Precise definitions

Integer types. char, short, int, long, long long, each signed or unsigned (plain char is a separate type from both signed char and unsigned char). The standard fixes only minimum ranges and nests them: the range of each type is contained in the range of the next one. In practice that gives:

  • sizeof(char) is 1 by definition. A short and an int have at least 16 bits, a long at least 32, a long long at least 64.
  • sizeof(short)≤sizeof(int)≤sizeof(long)≤sizeof(long long)\text{sizeof(short)} \le \text{sizeof(int)} \le \text{sizeof(long)} \le \text{sizeof(long long)}.
  • The usual GATE assumption is char 1 byte, short 2, int 4, float 4, double 8. A question that depends on long or pointer size states it (it is 8 bytes on 64-bit Linux and 4 on 32-bit systems).
  • Plain char may be signed or unsigned: this is implementation-defined (signed on x86, unsigned on many ARM compilers). Write signed char or unsigned char when it matters.

Value ranges. An nn-bit unsigned type holds 00 to 2n−12^n - 1. An nn-bit signed type in two's complement holds −2n−1-2^{n-1} to 2n−1−12^{n-1} - 1. For 8 bits that is 0 to 255 and -128 to 127. For 32 bits, INT_MAX is 231−1=21474836472^{31}-1 = 2147483647.

Integer promotion. An operand whose type ranks no higher than int (char, short, _Bool, bit-fields, enumerations, signed or unsigned) is converted to int if int can represent all its values, and otherwise to unsigned int. With a 32-bit int that means every char and short becomes int. Promotion is applied to the operands of arithmetic, bitwise and shift operators and of unary +, - and ~, before the operator runs. That is why c + 1 for a char c is an int.

Usual arithmetic conversions (C11 6.3.1.8). For a binary arithmetic operator, after promotion the two operands are brought to a common type. The decision uses the conversion rank of the integer types: char < short < int < long < long long, and a signed type and its unsigned partner have the same rank. In order:

  1. If either operand is long double, the other becomes long double. Otherwise if either is double, the other becomes double. Otherwise if either is float, the other becomes float.
  2. Otherwise both are integers. If they already have the same type, nothing changes.
  3. If both are signed, or both are unsigned, the one with the lower rank is converted to the type of the higher rank.
  4. If the unsigned operand has rank greater than or equal to the signed operand's rank, the signed operand is converted to the unsigned type. This is the common GATE case: int against unsigned int gives unsigned int, and int against unsigned long gives unsigned long.
  5. Otherwise (the signed type has the higher rank), if the signed type can represent every value of the unsigned type, the unsigned operand is converted to the signed type (for example long against unsigned int on a 64-bit machine).
  6. Otherwise both operands are converted to the unsigned type that corresponds to the signed type.

Overflow and conversion.

  • Unsigned arithmetic wraps: the result is taken modulo 2n2^n. This is fully defined.
  • Signed arithmetic overflow (for example INT_MAX + 1) is undefined behaviour.
  • Converting an out-of-range value to a signed type is implementation-defined (the standard also allows the implementation to raise a signal); gcc keeps the low bits, a two's complement wrap.
  • Converting a negative value to an unsigned type is defined: the result is the value plus 2n2^n ("keep the bits").

Division and remainder. Integer division truncates toward zero. The remainder takes the sign of the dividend, and (a/b)⋅b+a%b=a(a/b)\cdot b + a\%b = a holds whenever b≠0b \ne 0 and a/ba/b is representable. Division by zero is undefined, and so is INT_MIN / -1 (the quotient does not fit).

Shifts. x << n and x >> n are undefined if nn is negative or at least the width of the promoted left operand. For a signed x, x << n is defined only if x is non-negative and x⋅2nx \cdot 2^n fits in the result type; otherwise it is undefined (so shifting a negative value left, or shifting into the sign bit, is undefined). For an unsigned x, the result is taken modulo 2width2^{\text{width}}. Right-shifting a negative signed value is implementation-defined (gcc shifts arithmetically, copying the sign bit); right-shifting an unsigned value always fills with zeros.

Logical operators &&, || and ! yield int 0 or 1, and treat any non-zero operand as true. && and || evaluate left to right and short-circuit: the right operand is evaluated only if needed. There is a sequence point after the left operand.

Operator precedence (high to low, all binary levels group left to right):

  1. postfix () [] . -> ++ --
  2. prefix ++ -- + - ! ~ * & sizeof and casts (right to left)
  3. * / %
  4. + -
  5. << >>
  6. < <= > >=
  7. == !=
  8. &
  9. ^
  10. |
  11. &&
  12. ||
  13. ?: then = += ... (right to left) then ,

How it works

Step through the animation. Each step is one real evaluation: the numbers, the C type the compiler gave each sub-expression (read from the compiler with _Generic), and the actual bit pattern at that width. The sign bit is ringed. Bits that differ from the row above are shown in amber.

Values, types and bit patterns: unsigned char wraps around

Real values from gcc

C code

unsigned char c = 255;
c = c + 1;

Value, C type and bits (sign bit ringed, changed bits in amber)

cunsigned char= 255
11111111
c + 1int= 256
00000000
00000000
00000001
00000000
c after c = c + 1unsigned char= 0
00000000

Step 1

c is promoted to int before the addition, so c + 1 is computed in 32 bits and equals 256. Storing it back into an 8-bit unsigned char keeps only the low 8 bits, which are all zero. Unsigned arithmetic always wraps modulo 2^width.

1 / 10
real execution: values and C types printed by a gcc-compiled program (type names come from _Generic, widths from sizeof). gcc (Debian 14.2.0-19) 14.2.0, linux x64. gcc -std=gnu11 -O0 -o bits bits.c && ./bits

Three habits make these questions routine:

  1. Write the type next to every operand. c + 1 with a char c is int + int. -1 < 1u is unsigned + unsigned after conversion.
  2. Compute at that width. For unsigned int use arithmetic modulo 2322^{32}. For int, a result outside −231-2^{31} to 231−12^{31}-1 is undefined, so a well-posed question will not ask for it.
  3. Convert on assignment. Storing into a narrower unsigned type keeps the value modulo 2n2^n. Storing into a narrower signed type is implementation-defined (gcc keeps the low bits). Storing a double into an int truncates toward zero, and is undefined if the truncated value does not fit.

Worked examples

EX 1Ranges and wrap-around

Easy
#include <stdio.h>
 
int main(void) {
    signed char s = (signed char)200;
    unsigned char u = (unsigned char)-1;
    printf("%d %d\n", s, u);
    return 0;
}

Output:

-56 255

Step 1. 200 is 11001000 in 8 bits. As signed char the top bit is the sign bit, so the value is 200−256=−56200 - 256 = -56.

Step 2. -1 converted to unsigned char is −1+256=255-1 + 256 = 255 (all eight bits set).

Check. The unsigned conversion is fully defined: -1 becomes −1+256=255-1 + 256 = 255. The signed one is implementation-defined, because a signed 8-bit type cannot hold 200 (its maximum is 127); gcc, like every mainstream compiler, wraps by 256, and exam questions normally assume that.

EX 2Promotion hides the wrap-around

Medium
#include <stdio.h>
 
int main(void) {
    unsigned char a = 200, b = 100;
    printf("%d\n", a + b);
    printf("%d\n", (unsigned char)(a + b));
    return 0;
}

Output:

300
44

Step 1. a and b are promoted to int before +, so a + b is the int 300. Nothing wraps.

Step 2. The cast to unsigned char keeps only the low 8 bits: 300−256=44300 - 256 = 44.

Lesson. A narrow type only wraps when a value is stored back into it, not while the expression is evaluated.

EX 3Mixing int and unsigned int

Medium
#include <stdio.h>
 
int main(void) {
    int x = -5;
    unsigned int y = 3;
    printf("%d\n", x < y);
    printf("%u\n", x + y);
    printf("%d\n", (int)(x + y));
    return 0;
}

Output:

0
4294967294
-2

Step 1. Same size, mixed signedness: x is converted to unsigned int. −5+232=4294967291-5 + 2^{32} = 4294967291.

Step 2. x < y becomes 4294967291<34294967291 < 3, which is false: 0.

Step 3. x + y is 4294967291+3=42949672944294967291 + 3 = 4294967294 as an unsigned int. Printing with %u shows that number.

Step 4. Casting back to int gives 4294967294−232=−24294967294 - 2^{32} = -2 on gcc (converting an out-of-range value to a signed type is implementation-defined, and every mainstream compiler keeps the same bits).

EX 4Precedence of shifts, additions and bitwise operators

GATE level
#include <stdio.h>
 
int main(void) {
    int x = 4;
    printf("%d %d\n", x & 1 == 0, (x & 1) == 0);
    printf("%d %d\n", 1 + 2 << 3, 1 << 2 + 3);
    return 0;
}

Output:

0 1
24 32

Step 1. == is above &, so x & 1 == 0 is x & (1 == 0), that is 4 & 0 = 0. With parentheses, (4 & 1) is 0 and 0 == 0 is 1.

Step 2. + binds tighter than <<. So 1 + 2 << 3 is (1 + 2) << 3 = 3 << 3 = 24, and 1 << 2 + 3 is 1 << 5 = 32.

EX 5Integer versus floating division

GATE level
#include <stdio.h>
 
int main(void) {
    printf("%d %d %d\n", 7 / 2, -7 / 2, -7 % 3);
    double r1 = 7 / 2;
    double r2 = 7 / 2.0;
    printf("%.1f %.1f\n", r1, r2);
    return 0;
}

Output:

3 -3 -1
3.0 3.5

Step 1. Integer division truncates toward zero: 7/2=37/2 = 3 and −7/2=−3-7/2 = -3 (not -4).

Step 2. The remainder takes the sign of the dividend: −7%3=−1-7 \% 3 = -1, since (−7/3)⋅3+(−7%3)=−2⋅3+(−1)=−7(-7/3)\cdot 3 + (-7\%3) = -2\cdot 3 + (-1) = -7.

Step 3. In double r1 = 7 / 2; the division is done in int first (giving 3) and only then converted to double. The conversion to double does not rescue the fraction. 7 / 2.0 has a double operand, so 7 is converted first and the result is 3.5.

EX 6Short-circuit evaluation

GATE level
#include <stdio.h>
 
int main(void) {
    int i = 0, j = 5;
    if (i++ && j++) {
    }
    printf("%d %d\n", i, j);
 
    int k = 1, m = 5;
    if (k++ || m++) {
    }
    printf("%d %d\n", k, m);
    return 0;
}

Output:

1 5
2 5

Step 1. i++ yields the old value 0 (false) and sets i to 1. && already knows the result is false, so j++ is not evaluated: j stays 5.

Step 2. k++ yields 1 (true) and sets k to 2. || already knows the result is true, so m++ is not evaluated.

Rule. For && the right side runs only if the left is non-zero. For || it runs only if the left is zero.

Cheat box

Cheat box

  • nn-bit unsigned: 00 to 2n−12^n-1. nn-bit signed: −2n−1-2^{n-1} to 2n−1−12^{n-1}-1.
  • Anything narrower than int becomes int before the operator. It wraps only when stored back.
  • int with unsigned int: unsigned wins. So -1 < 1u is false and -1 > 0u is true.
  • Unsigned overflow wraps (defined). Signed overflow is undefined. Out-of-range conversion to signed is implementation-defined.
  • Integer division truncates toward zero. a % b has the sign of a. (a/b)⋅b+a%b=a(a/b)\cdot b + a\%b = a.
  • double r = 7 / 2; is 3.0, not 3.5.
  • Precedence, high to low: unary, * / %, + -, << >>, < <= > >=, == !=, &, ^, |, &&, ||, ?:, =, ,.
  • x & (x - 1) clears the lowest set bit. (x & (x - 1)) == 0 tests for a power of two or zero (for unsigned x).
  • && and || short-circuit and have a sequence point after the left operand.

Traps and GATE patterns

Other things examiners twist:

  • char c = 'A' + 2; stores the character code 67, which prints as C with %c and as 67 with %d.
  • A negative number printed with %u shows its unsigned reinterpretation; an unsigned value printed with %d can appear negative. A mismatched format is itself undefined behaviour in the standard, but GATE usually just wants the reinterpretation.
  • sizeof is an operator, not a function, and is evaluated at compile time (its operand is not executed, unless it is a variable-length array).

Connections

The bit-level view of numbers is covered in signed number representation and IEEE 754. The bitwise operators here are the software version of the Boolean algebra of logic gates, and the "anything can happen" rules continue on the page about undefined behaviour.

PYQs

8 previous year questions from your book. I re-solved each one myself (8 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2022 · MCQ · 2 marksQ1 · page 7 of the book
What is printed by the following ANSI C program?
#include<stdio.h>
int main (int argc, char *argv[])
{
char a= 'P';
char b= ' x ';
char c= (a & b)+' *';
char d= (b) -   ' -';
char e= (a∧b) + ' + ';
printf("%c %c %c \n", c, d, e);
return 0;
}
ASCII encoding for relevant characters is given
below.

A
B
C
.....
Z
65
66
67
.....
90

*
+
-
42
43
45
  • (a)z K S
  • (b)122 75 83
  • (c)* - +
  • (d)P x +
GATE 2017 · Set 2 · NATQ2 · page 7 of the book
Consider the following C program

#include<stdio.h>
int main ( ) {
int m= 10
int n, nl;
n=++m;

nl=m++;
n--;
--n1;
n-= nl;
printf("%d", n);
return 0;
}
The output of the program is ____.
GATE 2014 · MCQQ3 · page 7 of the book
Suppose n and p are unsigned int variables in a C
program. We wish to set p to nC3. If n is large, which
one of the following statements is most likely to set p
correctly?
  • (a)p= n*(n-1)*(n-2)/6;
  • (b)p= n*(n-1)/2*(n-2)/3;
  • (c)p= n*(n-1)/3*(n-2)/2;
  • (d)p= n*(n-1)*(n-2)/6.0;
GATE 2008 · MCQ · 2 marksQ4 · page 7 of the book
Which combination of the integer variables x, y,and z
makes the variable a get the value 4 in the following
expression?
a= (x> y)? ((x> z)? x: z): ((y> z)? y: z)
  • (a)x = 3, y = 4, z = 2
  • (b)x = 6, y = 5, z = 3
  • (c)x = 6, y = 3, z = 5
  • (d)x = 5, y = 4, z = 5 a b c ... ... z 97 98 99 ... ... 122
GATE 2019 · NAT · 2 marksQ7 · page 11 of the book
Consider the following C program:
#include <stdio.h>
 int main ()  {
     float sum = 0.0, j = 1.0, i = 2.0;
     while (i/j > 0.0625) {
           j = j + j;
          sum = sum + i/j;
          printf ("%f \n", sum);
       }
       return 0;
}

The number of times the variable sum will be printed,
when the above program is executed, is ______.
GATE 2017 · Set 1 · NATQ17 · page 20 of the book
Consider the following C program.

#include <stdio.h>
#include <string.h>
void printlength (char*s, char*t)
{
   unsigned int c = 0;
  int len = ((strlen(s) - strlen(t)) > c)?strlen(s):strlen(t);
  printf("%d\n", len);
}
void main ()  {
     char*x = "abc";
     char*y = "defgh";
     printlength(x,y);
}

Recall that strlen is defined in string.h as returning a
value of type size_t, which is an unsigned int. The
output of the program is _________.

    int pp(int a, int b) {
        int arr[20];
        int i, tot = 1, ex, len;
        ex= a;
        len = tob(b, arr);
    for (i=0; i < len; i++ ) {
            if (arr[i] == 1
                tot = tot * ex;
              ex= ex * ex;
        }
    } return (tot);
GATE 2016 · Set 1 · MCQ · 1 markQ22 · page 22 of the book
Consider the following C program.

void f(int, short);
void main ()
{
int i = 100;
short s = 12;
short *p = &s;
 __________ ;         // call to f()
}

Which one of the following expressions, when placed
in the blank above, will NOT result in a type checking
error?
  • (a)f(s, *s)
  • (b)i = f(i, s)
  • (c)f(i, *s)
  • (d)f(i, *p)
GATE 2014Q31 · page 23 of the book
Consider the function shown below :
int func(int num) {
int count = 0;
    while (num) {
          count ++;
        num>> = 1;

}
return (count);
}
The value returned by func (435) is _____.

Self-check

Question 1

What does the following print? printf("%d", (unsigned char)(255 + 1));

Show answer

255 + 1 is the int 256. The cast keeps the low 8 bits, which are all zero, so the output is 0.

#include <stdio.h>
 
int main(void) {
    printf("%d\n", (unsigned char)(255 + 1));
    return 0;
}
0

Question 2

With int a = -1; unsigned int b = 0;, what is the value of a > b?

Show answer

a is converted to unsigned: 4294967295, which is greater than 0. The value is 1, even though −1>0-1 > 0 is false in mathematics.

#include <stdio.h>
 
int main(void) {
    int a = -1;
    unsigned int b = 0;
    printf("%d\n", a > b);
    return 0;
}
1

Question 3

What is the value of 5 & 6 == 6?

Show answer

== binds first: 6 == 6 is 1. Then 5 & 1 is 1 (binary 101 and 001 share the lowest bit). If you had wrongly evaluated 5 & 6 first (giving 4) you would have computed 4 == 6, which is 0.

#include <stdio.h>
 
int main(void) {
    printf("%d\n", 5 & 6 == 6);
    return 0;
}
1

Question 4

After char c = 'A' + 2;, what do printf("%d %c", c, c) print?

Show answer

The code of 'A' is 65 in ASCII, so c holds 67. The output is 67 C.

#include <stdio.h>
 
int main(void) {
    char c = 'A' + 2;
    printf("%d %c\n", c, c);
    return 0;
}
67 C

Revision card

One-screen revision

  • Every value has a fixed width. nn-bit unsigned: 00 to 2n−12^n-1; signed: −2n−1-2^{n-1} to 2n−1−12^{n-1}-1.
  • Narrow types are promoted to int first. They wrap only on storage.
  • Mixed int and unsigned int: convert to unsigned. Mixed with double: convert to double.
  • Unsigned overflow wraps. Signed overflow is undefined. Division truncates toward zero.
  • 7 / 2 is 3 even when assigned to a double.
  • Precedence: bitwise operators are below ==. Shifts are below +.
  • && and || short-circuit.
  • Think in three steps: type of each operand, operation at that type, conversion on store.

References

  • ·Kernighan and Ritchie, The C Programming Language, 2nd ed., Chapter 2 Types, Operators and Expressions
  • ·ISO/IEC 9899:2011 (C11), Section 6.3 Conversions (6.3.1.1 integer promotions, 6.3.1.8 usual arithmetic conversions), Section 6.5 Expressions
  • ·Bryant and O'Hallaron, Computer Systems: A Programmer's Perspective, 3rd ed., Chapter 2 Representing and Manipulating Information

Verification

All C programs on this page are compiled with -Wall -Wextra -Werror and run by npm run verify:c, and the shown outputs match. The values, types and bit patterns in the animation are printed by a real gcc program (types via _Generic, widths via sizeof). The prose, the precedence and conversion summaries and the GATE-pattern claims have NOT yet had a human textbook check (the independent second pass is described at the end of these notes). SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). No defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never

My notes (saved in this browser)