In short
C is a small, statically typed, compiled procedural language created by Dennis Ritchie at Bell Labs (1972) and standardised as ANSI C (1989) / ISO C90, then C99, C11, C17 and C23. It gives direct control over memory and hardware with almost no runtime, which is why operating systems, embedded firmware, databases and interpreters (CPython included) are written in it. Every Indian engineering degree teaches it in the first year as "Programming for Problem Solving" (AICTE model curriculum), and it remains the base language for GATE CS programming questions, for aptitude-plus-coding rounds (TCS NQT, Infosys, Wipro, Cognizant, Accenture) and for embedded interviews (Bosch, Continental, Tata Elxsi, KPIT, Qualcomm, BEL, ISRO, DRDO, C-DAC). Interviewers use C precisely because it exposes types, sizes, overflow and evaluation order that higher-level languages hide.
Explained
The language, standards and the shape of a program
- Features: procedural, statically typed, block-structured, compiled; small keyword set (32 in C90; 37 in C99, adding
inline,restrict,_Bool,_Complex,_Imaginary; 44 in C11, adding_Generic,_Atomic,_Static_assertetc.); rich operator set; separate compilation; a standard library rather than built-in I/O keywords. - Standards: C89/C90 (ANSI/ISO), C99 (
//comments,long long, declarations anywhere, variable-length arrays,stdbool.h,inline), C11 (_Static_assert, anonymous structs, threads,gets()removed), C17 (bug-fix edition), C23 (ISO/IEC 9899:2024 —true/falsekeywords,nullptr, binary literals; compiler support still partial, so exams stick to C99/C11). - Minimum program structure: preprocessor directives → global declarations →
int main(void) { ... return 0; }→ other functions.mainreturns an exit status to the OS (0 = success). - Identifiers: letters, digits, underscore; cannot start with a digit; case-sensitive; keywords not allowed; names starting with
_are reserved by the standard. - Statement and block: every statement ends with
;;{ }creates a block with its own scope. - Comments:
/* ... */(all versions),// ...(C99 onwards). - Scope: block (local), function, file (
staticglobals), program (external). Lifetime is separate from scope — see storage classes.
Data types, constants and type conversion
- Basic types:
char,int,float,double,void; modifiersshort,long,long long,signed,unsigned. - Sizes are implementation-defined, not fixed by the standard: typical 64-bit Linux/GCC —
char1,short2,int4,long8,long long8,float4,double8 bytes. Only guarantees:sizeof(char)==1andchar ≤ short ≤ int ≤ long ≤ long long. Use<stdint.h>(int32_t,uint8_t) when width must be exact — standard practice in embedded code. - Derived types: arrays, functions, pointers (own note). User-defined:
struct,union,enum,typedef. - Signed integers use two's complement (mandated from C23, universal in practice); signed overflow is undefined behaviour, unsigned overflow wraps modulo 2n.
- Constants: integer (
42,052octal,0x2Ahex,42U,42L), floating (3.14,2.5e-3,1.5f), character ('A', escape sequences\n \t \\ \0), string literal ("text"— achararray ending in'\0'). - **
constmakes an object read-only;volatile** tells the compiler the value may change outside program control (hardware registers, interrupt flags) so it must not be optimised into a register. - Implicit conversion (usual arithmetic conversions):
char/shortpromote toint; in a mixed expression the operand of lower rank converts to the higher:int → unsigned int → long → unsigned long → float → double → long double. Mixing signed and unsigned of the same rank converts the signed value to unsigned — a classic exam trap. - Explicit conversion: cast
(float)a / b. Narrowing truncates ((int)3.9 == 3, toward zero) and may overflow.
Operators, expressions and precedence
- Arithmetic:
+ - * / %. Integer/truncates toward zero;%works only on integers and in C99 takes the sign of the dividend (-7 % 3 == -1). - Relational
< <= > >=, equality== !=; result isint0 or 1. Logical&& || !—&&and||short-circuit left to right and are sequence points. - Bitwise:
& | ^ ~ << >>.x << kmultiplies by 2k;x >> kdivides unsigned by 2k (right shift of a negative value is implementation-defined). Shifting by ≥ width is undefined. - Assignment:
=and compound+= -= *= /= %= &= |= ^= <<= >>=. - Increment/decrement: prefix
++i(increment, then use) vs postfixi++(use, then increment). - Others: conditional
c ? a : b; comma,;sizeof(compile-time, yieldssize_t); member access.and->; address&and indirection*. - Precedence (high → low): postfix
() [] . ->→ unary! ~ ++ -- + - * & sizeof (cast)→* / %→+ -→<< >>→< <= > >=→== !=→&→^→|→&&→||→?:→ assignment →,. Unary and assignment associate right-to-left; the rest left-to-right. - Precedence is not evaluation order. In
f() + g()the call order is unspecified; modifying the same object twice without an intervening sequence point (i = i++,a[i] = i++) is undefined behaviour. Interviewers ask these to test whether you know they have *no* correct answer.
Control flow: decisions, loops and jumps
- **
if/else if/else:** any non-zero value is true. A danglingelsebinds to the nearest unmatchedif. - **
switch:** controlling expression must be an integer type;caselabels must be distinct integer constant expressions; execution falls through untilbreak;defaultis optional and may appear anywhere. Cannot switch on afloator a string. - **
while(entry-controlled, test first),do...while** (exit-controlled, body runs at least once, needs trailing;), **for (init; test; update)** — all three parts optional;for(;;)is an infinite loop. C99 allows declaring the counter insidefor. - Jumps:
breakleaves the innermost loop or switch;continueskips to the next iteration (to the update expression in afor);returnexits the function;goto labeljumps within the same function (used almost only for a single-exit error path in system code). - Nesting and counting: a nested loop's total iterations multiply; the number of iterations of
for(i=a; i<b; i+=s)is ⌈(b−a)/s⌉.
Functions, recursion and storage classes
- Declaration (prototype) tells the compiler the return type and parameter types; definition supplies the body; call transfers control. A prototype is mandatory in practice — the implicit-
intrule was removed in C99. - Parameter passing is strictly call by value: the function gets a copy, so swapping two caller variables requires passing their addresses (pointers note). Arrays are the exception in appearance only — an array argument decays to a pointer to its first element, so the callee sees no size and
sizeofinside the function gives the pointer size. - **
void** as return type = returns nothing; as parameter list = takes no arguments (int f(void), unlikeint f()which in C90 means "unspecified"). - Recursion: a function calling itself, needing (1) a base case and (2) progress toward it. Each call has its own copy of the automatic variables on the stack; too deep a recursion exhausts it. Classic cases: factorial, Fibonacci, GCD, Tower of Hanoi (2n − 1 moves), binary search, Ackermann.
- Recursion vs iteration: recursion is shorter and natural for trees and divide-and-conquer but costs stack frames and call overhead; iteration is faster and bounded in memory. Tail recursion can be optimised into a loop by the compiler (not guaranteed by the standard).
- Storage classes —
auto(default for locals; automatic lifetime),register(hint only; address cannot be taken),static(lifetime = whole program; inside a function it keeps its value between calls, at file level it restricts linkage to that file),extern(declares an object defined elsewhere; no storage allocated). Uninitialisedstatic/global objects are zero-initialised; uninitialised automatic objects hold garbage.
Arrays, strings, structures, unions and enums
- Array: a fixed-size contiguous block of same-type elements, indexed from 0 to n−1.
int a[5] = {1,2,3};sets the rest to 0;int a[] = {1,2,3}sizes itself. C does no bounds checking —a[7]on a 5-element array is undefined behaviour, a buffer overrun, and the root of most C security bugs. - Multidimensional arrays are stored row-major:
int a[3][4]is three rows of four. In a function parameter all dimensions except the first must be given:void f(int a[][4], int rows). - Variable-length arrays (
int a[n]) are C99; optional in C11 onwards, so avoid them in portable code. - Strings are
chararrays terminated by'\0', so an n-character string needs n+1 bytes.char s[] = "abc"is modifiable;char *s = "abc"points to a read-only literal — writing through it crashes. - **
<string.h>:**strlen(excludes'\0'),strcpy/strncpy,strcat/strncat,strcmp(returns <0, 0, >0 by character codes, not alphabetical case-insensitive order),strchr,strstr,strtok,memcpy,memset. You cannot compare strings with==or assign with=. - **
structure:** groups members of different types under one name; accessed with.(or->through a pointer); assignable and passable as a whole (copied member-wise); may be nested; may contain bit-fields (unsigned flag : 1;) for register maps. - Padding and alignment: the compiler inserts unnamed bytes so each member sits on its natural boundary, so
sizeof(struct)≥ sum of member sizes. Ordering members from largest to smallest reduces waste. - **
union:** all members share the same storage, so its size is that of the largest member and only the last-written member is valid — used for type-punning registers and tagged variants. - **
enum:** named integer constants, numbered from 0 unless assigned (enum day {MON=1, TUE, WED};→ 1, 2, 3). **typedef** creates an alias (typedef struct node Node;), improving readability; it does not create a new type.
Input/output, files and the preprocessor
- Formatted I/O:
printf("fmt", args)returns the number of characters written;scanf("fmt", &var)returns the number of items successfully assigned — always check it. Specifiers:%dint,%uunsigned,%ldlong,%f/%lfdouble,%eexponent,%cchar,%sstring,%xhex,%ooctal,%ppointer,%%literal percent,%zuforsize_t. Width and precision:%5.2f,%-10s. - A wrong specifier is undefined behaviour, not a conversion.
scanf("%d")without&corrupts memory;%sskips whitespace and has no length limit unless you write%49s. - Character and line I/O:
getchar,putchar,fgets(safe),puts. **gets()was removed in C11** — never use it. - Files:
FILE *fp = fopen("data.txt","r");modesr w a r+ w+ a+andbfor binary; checkfp != NULL; read/write withfscanf/fprintf,fgets/fputs,fread/fwrite; position withfseek,ftell,rewind; detect end withfeof/ferror; alwaysfclose. Streamsstdin,stdout,stderrare opened automatically. - Preprocessor directives (textual, before compilation):
#include <...>for system and#include "..."for project headers;#define PI 3.14159object-like and#define SQ(x) ((x)*(x))function-like macros (parenthesise every parameter and the whole body);#undef; conditional compilation#if / #ifdef / #ifndef / #else / #elif / #endifwith include guards;#pragma. Macros are type-blind and may evaluate an argument twice — preferconstandinlinefunctions where possible. - Command-line arguments:
int main(int argc, char *argv[])—argv[0]is the program name,argccounts it.
Key points
- Standard - ISO C: C90 → C99 → C11 → C17 → C23; exams and most compilers assume C99/C11.
- Statically typed, weakly checked - types are fixed at compile time but casts and no bounds checking let you bypass them.
- Type sizes are implementation-defined - only
sizeof(char)==1is guaranteed; use<stdint.h>for fixed widths. - Two's complement - negative value = ~x + 1; signed n-bit range −2n−1 … 2n−1−1.
- Signed/unsigned mixing - the signed operand becomes unsigned, so
-1 < 1uis false. - Integer division truncates -
7/2 == 3; force real division with a cast or a float literal. - Precedence ≠ evaluation order -
i = i++andf() + g()have no defined behaviour/order. - Short-circuiting -
&&and||stop as soon as the result is known; the right operand may never run. - **
do...while** - the only loop whose body is guaranteed to execute once. - **
switchfalls through** - withoutbreak, control runs into the next case. - Call by value only - pass an address to let a function modify the caller's variable.
- Arrays decay to pointers in function parameters, so size information is lost.
- Indexing starts at 0, and out-of-range access is undefined behaviour, not an error.
- **Strings are
'\0'-terminated char arrays** -strlen= characters,sizeof= bytes including the terminator. - **
staticlocal - keeps its value between calls;staticglobal** - visible only in that file. - Uninitialised locals hold garbage; globals and statics are zero.
- **
structsums members with padding;unionoverlays them** and is as big as its largest member. - Undefined behaviour - the program may do anything; it is the single biggest source of C exam and interview questions.
Formulas and examples
Two's complement of x (n bits) = 2n − x = (~x) + 1 ; symbols: n = bits, ~ = bitwise NOT
Value of a signed 8-bit pattern p: if p > 127 then value = p − 256
Integer division identity: a == (a / b) * b + (a % b), for b ≠ 0
Address of a[i] = base + i × sizeof(element) (bytes)
Address of a[i][j] in
T a[R][C] (row-major) = base + (i × C + j) × sizeof(T)Total array bytes = number of elements × sizeof(element) ; bytes for an n-char string = n + 1
Iterations of
for(i=a; i<b; i+=s) = ⌈(b − a) / s⌉ , s > 0x << k == x × 2k ; x >> k == x / 2k (unsigned) ; x & (x−1) clears the lowest set bit
Recursive-call count of naive Fibonacci: C(0)=C(1)=1, C(n)=1+C(n−1)+C(n−2)
Example 1 — signed/unsigned comparison.
unsigned int u = 10; int i = -20;
printf("%d", i < u); /* prints 0 */
Both operands have rank unsigned int, so i converts to unsigned: −20 + 232 = 4,294,967,276. Since 4,294,967,276 < 10 is false, the output is 0. Same reason sizeof(x) - 10 > 0 is true for every x except one of exactly 10 bytes: size_t is unsigned, so the subtraction wraps instead of going negative.
Example 2 — overflow on a narrow type. signed char c = 200; — signed char holds −128…127, and 200 > 127, so the value is reduced modulo 256: 200 − 256 = −56 (bit pattern 1100 1000). Printing it with %d gives −56; assigning it to unsigned char would give 200.
Example 3 — digits of a number (loop trace), n = 9875.
| step | n | digit = n % 10 | sum | rev = rev*10 + digit | n = n / 10 |
|---|---|---|---|---|---|
| 1 | 9875 | 5 | 5 | 5 | 987 |
| 2 | 987 | 7 | 12 | 57 | 98 |
| 3 | 98 | 8 | 20 | 578 | 9 |
| 4 | 9 | 9 | 29 | 5789 | 0 |
Sum of digits = 29, reverse = 5789, loop runs 4 times (= number of digits).
Example 4 — array addressing. int a[3][4]; with base address 2000 and sizeof(int) = 4:
&a[0][0]= 2000;&a[1][2]= 2000 + (1×4 + 2)×4 = 2000 + 24 = 2024;&a[2][3]= 2000 + (2×4+3)×4 = 2044.sizeof(a)= 3×4×4 = 48 bytes;sizeof(a[0])= 16; total elements 12.
**Example 5 — strlen vs sizeof and structure padding.**
char s[] = "ABLjobs"; /* strlen(s) = 7, sizeof(s) = 8 */
struct A { char c; int i; char d; }; /* typical 4-byte int alignment */
Layout: c at offset 0, 3 padding bytes, i at 4–7, d at 8, 3 trailing padding bytes → sizeof(struct A) = 12 bytes, against 6 bytes of real data. Reordering as { int i; char c; char d; } gives 8 bytes. Exact figures are compiler- and ABI-dependent.
Example 6 — bit tricks.
- Count set bits in 13 (
1101): 13 & 12 = 12 (1100), 12 & 11 = 8 (1000), 8 & 7 = 0 → 3 set bits, 3 iterations ofx &= x-1. - Power of two test:
x && !(x & (x-1))→ true for 8 (1000&0111= 0), false for 12. - XOR swap with a = 5, b = 7: a = 57 = 2; b = 27 = 5; a = 25 = 7 → a = 7, b = 5.
Example 7 — recursion cost. fib(5) by naive recursion: C(2)=3, C(3)=5, C(4)=9, C(5)=15 calls to return the value 5; the iterative version needs 4 additions. This is the standard "why is recursion expensive" answer.
| Construct | Test | Minimum runs | Typical use |
|---|---|---|---|
while | before body | 0 | unknown count, condition-driven |
do...while | after body | 1 | menus, "retry until valid input" |
for | before body | 0 | known count, index iteration |
| Feature | struct | union |
|---|---|---|
| Memory | sum of members + padding | size of largest member |
| Members valid | all at once | only the last one written |
| Use | records, nodes, config | register overlay, tagged value, memory saving |
Where it is used
- Embedded and automotive firmware: engine/BMS/ECU code, RTOS tasks and device drivers at Bosch, Continental, KPIT, Tata Elxsi, Ashok Leyland and L&T Technology Services —
volatile, bit-fields, fixed-width types and unions over hardware registers. - Systems and networking: Linux kernel and drivers, TCP/IP stacks, firmware in telecom and defence electronics (BEL, HAL, DRDO labs, ISRO/VSSC flight software, where MISRA-C style rules apply).
- Performance cores of higher-level stacks: CPython, PostgreSQL/MySQL, SQLite, Redis, OpenSSL and NumPy's inner loops are C; Python/Java extension modules are written in C.
- Placement and competitive coding: TCS NQT and CodeVita, Infosys/Wipro/Capgemini coding rounds, GATE CS (output prediction, recursion traces,
switch/loop counting), and PSU/ISRO written tests. - Academic and lab work: AICTE first-year "Programming for Problem Solving", data-structures labs (stacks, queues, linked lists, sorting) and microprocessor/microcontroller labs; GCC or Turbo C in college labs — Turbo C's 16-bit 2-byte
intexplains many old textbook answers. - Job roles: embedded software engineer, firmware developer, systems programmer, device-driver engineer, IoT developer, and QA roles using C test harnesses.
Interview questions
- Q: Is C call by value or call by reference? A: Strictly call by value; "reference" behaviour is achieved by passing the address of a variable so the function can modify it through the address.
- Q: Why does
printf("%f", 7/2)not print 3.5? A:7/2is integer division giving 3, and passing anintto%fis undefined behaviour; write7/2.0and the result is 3.5. - Q: What is the output of
int i = 5; printf("%d", i++ + ++i);? A: It has none that is defined —iis modified twice without an intervening sequence point, so the behaviour is undefined; any printed value is compiler-specific. - Q: Difference between
sizeofandstrlen? A:sizeofis a compile-time operator giving bytes of storage (including the'\0'of an array literal);strlenis a runtime library function counting characters up to, but excluding,'\0'. - Q: What does
staticdo to a local variable and to a global one? A: On a local it gives program lifetime so the value persists between calls; on a global or function it restricts linkage to that translation unit. - Q: Why is
volatileneeded in embedded C? A: It stops the compiler caching the value in a register or optimising reads away, which is essential for hardware registers and variables changed by interrupts. - Q:
structvsunion— when would you choose a union? A: When only one of several representations is valid at a time and memory matters, for example overlaying a 32-bit register as four bytes or a tagged value. - Q: Why is
sizeof(struct {char c; int i;})8 and not 5? A: The compiler pads aftercso thatistarts on a 4-byte boundary, and pads the structure to a multiple of its strictest alignment. - Q: What happens if you write past the end of an array? A: Nothing is checked; it is undefined behaviour that may corrupt adjacent data, crash, or silently appear to work — the classic buffer overflow.
- Q: Why is
gets()banned? A: It cannot limit input length, so any longer line overruns the buffer; it was removed in C11 andfgetsmust be used. - Q: Can you
switchon a string or a float? A: No — the controlling expression must be an integer type and case labels must be integer constant expressions; useif/else ifwithstrcmpinstead. - Q: Convert a recursive factorial to iterative and state the gain. A:
for(f=1,i=2;i<=n;i++) f*=i;— it removes n stack frames and the call overhead, running in O(1) extra space. - Q: What does
x & (x-1)do? A: It clears the lowest set bit, so a loop of it counts set bits andx && !(x&(x-1))tests for a power of two.
Common mistakes
- Using
=instead of==in a condition —if (x = 0)assigns and is always false; and writingif (a == b == c), which compares(a==b)withc. - Forgetting
&inscanf("%d", &n), or using%ffor adoubleread (needs%lf), or not checkingscanf's return value. - Expecting real division from integers, or assuming
%works on floats. - Treating strings as values — comparing with
==, assigning with=, or forgetting the extra byte for'\0'and then+1size. - Returning the address of a local variable, or using an uninitialised local and assuming it is 0 (only globals and statics are zeroed).
- Omitting
breakin aswitch, or putting a stray;afterfor(...)/while(...), which makes an empty body. - Writing unparenthesised macros —
#define SQ(x) x*xmakesSQ(a+b)expand toa+b*a+b. - Quoting a type size as fixed (assuming
intis always 4 bytes, or copying Turbo C's 2-byte answers) and ignoring off-by-one loop bounds likei <= non ann-element array.
Related topics
Pointers and dynamic memory · Arrays, linked lists, stacks and queues · Compilation, linking and memory layout · Exception handling and debugging · C++ essentials · Number systems and computer arithmetic