Skip to content

C Quick Guide

C is a general-purpose, procedural programming language known for efficiency and low-level memory access. Developed by Dennis Ritchie at Bell Labs in 1972 for the UNIX OS.

Standardized by ANSI (C89) and ISO, with later revisions like C99, C11, C17 adding features.

Widely used for system programming, embedded systems, performance-critical applications, and foundational software.

  • Efficient
  • Low-level memory access (pointers)
  • Structured (functions)
  • Portable
  • Foundation for many other languages
  • Invented to write UNIX.
  • Successor to the B language.
  • Standardized by ANSI (1989) and ISO.
  • Core language for Linux, macOS, Windows kernels.
  • Used in databases, compilers, game engines.

To write, compile, and run C programs, you need: (a) A Text Editor and (b) A C Compiler.

Used to write your source code (files ending in .c). Modern editors offer syntax highlighting, auto-completion, etc.

Popular Choices: Visual Studio Code (VS Code), Sublime Text, Atom, Vim, Emacs, Notepad++ (Windows).

Source files contain human-readable code. Ensure you know how to save files with the .c extension.

Translates your .c source files into machine code (an executable program) that the computer’s CPU can run.

Common Compilers:

  • GCC (GNU Compiler Collection): Free, open-source, widely available on Linux, macOS, and Windows (via MinGW-w64/MSYS2 or WSL). Supports C and C++.
  • Clang: Another popular open-source compiler, known for fast compilation and good diagnostics. Often used with LLVM.
  • MSVC (Microsoft Visual C++): Part of Visual Studio on Windows. Compiles C and C++.

Installation varies by Operating System:

Use your distribution’s package manager. Check if GCC is installed:

$ gcc --version

If not installed, use commands like:

# Debian/Ubuntu
sudo apt update
sudo apt install build-essential
# Fedora/CentOS/RHEL
sudo dnf groupinstall "Development Tools"
# Arch Linux
sudo pacman -S base-devel

Install Xcode from the App Store, or just the Command Line Tools. Open Terminal and run:

$ xcode-select --install

This provides GCC (often an alias for Clang) and other development tools.

  • Recommended: WSL (Windows Subsystem for Linux): Install a Linux distribution (like Ubuntu) from the Microsoft Store. Then follow the Linux installation steps above within WSL.
  • MSYS2/MinGW-w64: Provides an up-to-date GCC toolchain native to Windows. Download from https://www.msys2.org/, follow their instructions to install the mingw-w64-x86_64-toolchain package. Ensure the MinGW bin directory (e.g., C:\msys64\mingw64\bin) is added to your system’s PATH environment variable.
  • Visual Studio Community: Download from https://visualstudio.microsoft.com/vs/community/. During installation, select the “Desktop development with C++” workload, which includes the C compiler (cl.exe) and build tools.

Let’s examine a minimal C program.

A basic C program structure includes:

  • Preprocessor Directives (e.g., #include)
  • Functions (at least main)
  • Variables (optional)
  • Statements & Expressions
  • Comments

Example hello.c:

// Include standard input/output library for printf
#include <stdio.h>
// main function: execution starts here
int main(void) {
/* Multi-line comment:
This program prints Hello, World! */
printf("Hello, World!\n"); // Statement calling printf function
return 0; // Indicate successful execution
}

Explanation:

  • #include <stdio.h>: A preprocessor directive telling the compiler to include the contents of the standard input/output header file, which contains the declaration for printf.
  • int main(void): The main function where program execution begins. int means it returns an integer status code; void means it takes no arguments (alternatively int main(int argc, char *argv[]) for command-line arguments).
  • // ... and /* ... */: Comments ignored by the compiler, used for explanation.
  • printf(...): A standard library function to print output to the console. \n is an escape sequence for a newline character.
  • return 0;: Exits the main function and returns 0 to the operating system, conventionally indicating successful execution.

Steps:

  1. Save the code above in a file named hello.c.
  2. Open a terminal or command prompt.
  3. Navigate to the directory where you saved the file.
  4. Compile the code using the C compiler (e.g., GCC): gcc hello.c -o hello (or clang hello.c -o hello). The -o hello part specifies the name of the output executable file.
  5. If there are no errors, an executable file named hello (or hello.exe on Windows) will be created.
  6. Run the program: ./hello (on Linux/macOS/WSL) or hello.exe (on Windows command prompt).

Expected Output:

$ gcc hello.c -o hello
$ ./hello
Hello, World!

Ensure the compiler (gcc, clang, etc.) is in your system’s PATH.

Understanding the basic building blocks of C syntax.

A C program is composed of tokens. Tokens are the smallest individual units in a program, such as keywords, identifiers, constants, string literals, and symbols. For example, the statement:

printf("Hello, World!\n");

Consists of the following tokens:

printf
(
"Hello, World!\n"
)
;

In C, the semicolon acts as a statement terminator. Each distinct statement must end with a semicolon.

Example of two statements:

printf("Hello!\n");
return 0;

Comments are explanatory text ignored by the compiler. C supports two types:

/* This is a multi-line comment.
It can span several lines. */
// This is a single-line comment (introduced in C99). It ends at the newline.

Comments cannot be nested (a /* inside another /* ... */ block is usually problematic).

An identifier is a name used for variables, functions, labels, and other user-defined items. Rules:

  • Must start with a letter (a-z, A-Z) or an underscore (_).
  • Subsequent characters can be letters, underscores, or digits (0-9).
  • Cannot contain punctuation characters like @, $, %.
  • Are case-sensitive: myVar and myvar are different identifiers.
  • Cannot be the same as C keywords.

Examples of valid identifiers:

count _temp value1 calculate_sum MAX_ENTRIES

Keywords are reserved words with special meanings in C. They cannot be used as identifiers.

Standard Keywords (C11 and earlier)
autodoubleintstruct
breakelselongswitch
caseenumregistertypedef
charexternreturnunion
constfloatshortunsigned
continueforsignedvoid
defaultgotosizeofvolatile
doifstaticwhile
_Bool_Complex_Imaginaryinline (C99)
restrict (C99)

Note: Keywords starting with an underscore are often reserved or part of later standards.

Whitespace refers to blanks (spaces), tabs, newline characters, and comments. The C compiler mostly ignores whitespace, but it’s crucial for separating tokens.

For example, in int age;, whitespace between int and age is required. However, in result = value1 + value2;, whitespace around = and + is optional but improves readability.

int age; // Whitespace needed
fruit=apples+oranges; // Works, but less readable
fruit = apples + oranges; // Better readability

Blank lines are also ignored and can be used to structure code.

Data types define the kind of data a variable can hold and the operations that can be performed on it. They determine memory size and interpretation of bit patterns.

C types can be broadly classified:

S.N.CategoryDescription
1Basic TypesArithmetic types: Further divided into Integer Types and Floating-Point Types.
2Enumerated Types (enum)User-defined integer types where variables can only take values from a specific set of named integer constants.
3Void Type (void)Indicates absence of type/value. Used for functions returning nothing, functions with no parameters, or generic pointers (void*).
4Derived TypesConstructed from basic types: Pointers (*), Arrays ([]), Structures (struct), Unions (union), and Function Types.

Aggregate types (arrays and structures) group multiple data items. Function types describe a function’s return type and parameters.

Represent whole numbers. Standard integer types include:

TypeMinimum Size (Standard)Minimum Range (Standard) & Common Values
charTypically 1 byteUsually -128 to 127 (signed) or 0 to 255 (unsigned). Implementation-defined whether plain char is signed or unsigned.
unsigned char1 byte0 to 255
signed char1 byte-128 to 127
short (short int)At least 2 bytesAt least -32,768 to 32,767
unsigned shortAt least 2 bytesAt least 0 to 65,535
intTypically system’s natural word size (often 4 bytes on 32-bit/64-bit systems)At least -32,768 to 32,767 (like short), commonly larger (-2,147,483,648 to 2,147,483,647 for 4 bytes)
unsigned intSame size as intAt least 0 to 65,535 (like unsigned short), commonly 0 to 4,294,967,295 (for 4 bytes)
long (long int)At least 4 bytesAt least -2,147,483,648 to 2,147,483,647
unsigned longAt least 4 bytesAt least 0 to 4,294,967,295
long long (long long int) (C99)At least 8 bytesAt least -(2^63) to (2^63)-1
unsigned long long (C99)At least 8 bytesAt least 0 to (2^64)-1

The exact sizes and ranges depend on the compiler and platform. Use the sizeof operator to find the size in bytes on your system: sizeof(int), sizeof(char*).

For portability and predictable sizes, use types from <stdint.h> when exact widths are needed:

  • int8_t, int16_t, int32_t, int64_t: Signed integers of exactly 8, 16, 32, 64 bits.
  • uint8_t, uint16_t, uint32_t, uint64_t: Unsigned integers of exactly 8, 16, 32, 64 bits.
  • intptr_t, uintptr_t: Integer types capable of holding object pointers.

Example:

#include <stdint.h>
#include <stdio.h>
int main(void) {
int32_t file_offset = -1024;
uint8_t pixel_value = 255;
printf("Offset: %d, Pixel: %u\n", file_offset, pixel_value);
printf("Size of int32_t: %zu bytes\n", sizeof(int32_t)); // %zu for size_t
return 0;
}

Represent numbers with fractional parts (real numbers).

TypeTypical SizeTypical PrecisionDescription
floatTypically 4 bytes~6-7 decimal digits precisionSingle-precision
doubleTypically 8 bytes~15-16 decimal digits precisionDouble-precision
long doubleOften 10, 12, or 16 bytesHigher precision than double (platform dependent)Extended-precision

The header file <float.h> defines macros (like FLT_MIN, DBL_MAX, LDBL_DIG) providing details about floating-point representation on the specific platform.

C99 introduced a standard boolean type.

  • Include <stdbool.h>.
  • Type: bool
  • Values: true, false

Example:

#include <stdbool.h>
#include <stdio.h>
int main(void) {
bool is_active = true;
if (is_active) {
printf("System is active.\n");
}
return 0;
}

Internally, bool is often an integer type, with false being 0 and true typically 1.

void signifies the absence of a type or value. Uses:

S.N.ContextDescription
1Function Return TypeIndicates a function does not return a value. Example: void print_message(const char *msg);
2Function ParametersIndicates a function takes no parameters. Example: int get_random_number(void);
3Generic Pointers (void *)A pointer to void can hold the address of any data type, but cannot be dereferenced directly. It must be cast to a specific pointer type first. Example: malloc returns void *. void *ptr = malloc(100); int *int_ptr = (int*)ptr;

Understanding void * is fundamental for dynamic memory allocation and generic functions.

A variable is a named storage location in memory that holds a value. Programs manipulate these values. Every variable in C has a specific data type, which dictates its size, memory layout, range of possible values, and the operations applicable to it.

Variable names (identifiers) follow specific rules: start with a letter or underscore, followed by letters, digits, or underscores. C is case-sensitive (age and Age are different variables).

Basic variable types correspond to the basic data types:

TypeDescription
char, signed char, unsigned charStores single characters or small integers.
short, int, long, long long (and their unsigned variants)Stores whole numbers of varying sizes.
float, double, long doubleStores numbers with fractional parts.
bool (from <stdbool.h>)Stores boolean values (true or false).

C also supports derived types like pointers, arrays, structures, etc., allowing for more complex variable definitions.

A definition reserves memory for the variable and associates the name with that memory location. It specifies the data type and variable name(s):

type variable_list;

type is a valid C data type (e.g., int, float, char, struct MyData). variable_list is one or more identifiers separated by commas.

Examples of definitions:

#include <stdbool.h> // For bool
#include <stdint.h> // For uint8_t
int i, j, k; // Define three integers
char c, initial; // Define two characters
float salary, tax; // Define two single-precision floats
double pi; // Define one double-precision float
bool is_valid; // Define a boolean variable
uint8_t status_code; // Define an 8-bit unsigned integer

Variables can be initialized (given an initial value) at the time of definition using the assignment operator (=):

type variable_name = value;

Examples:

int count = 0; // Define and initialize count to 0
char grade = 'A'; // Define and initialize grade to 'A'
float temperature = 98.6f; // Define and initialize temperature (use 'f' suffix for float literals)
double planck_constant = 6.62607015e-34;
int x = 10, y = 20; // Define and initialize multiple variables
// Initialization for derived types
int numbers[3] = {1, 2, 3}; // Initialize an array
char message[] = "Hello"; // Initialize a character array (string)

If a variable with static storage duration (like global variables or variables declared with static) is not explicitly initialized, it’s implicitly initialized to zero (or NULL for pointers). Local variables (automatic storage duration) have undefined initial values if not explicitly initialized – using them before assignment leads to unpredictable behavior.

A declaration introduces a variable’s name and type to the compiler but doesn’t necessarily reserve storage (though definition often implies declaration). Declarations are crucial when using variables defined in other files or before their definition point.

The extern keyword is used for declarations that refer to variables defined elsewhere:

extern int global_counter; // Declares global_counter defined in another file
extern const char *app_name; // Declares app_name defined elsewhere

A variable can be declared multiple times (e.g., in multiple header files using extern), but it must be defined exactly once in one source file.

// --- main.c ---
#include <stdio.h>
// Declaration of variable defined in another_file.c
extern int shared_value;
extern void print_shared_value(void); // Function declaration
int main(void) {
printf("Initial shared value (declared extern): %d\n", shared_value);
shared_value = 100; // Modify the shared variable
print_shared_value();
return 0;
}
// --- another_file.c ---
#include <stdio.h>
// Definition of the shared variable
int shared_value = 42; // Definition and initialization
// Definition of the function
void print_shared_value(void) {
printf("Current shared value (defined here): %d\n", shared_value);
}

To compile this, you’d typically compile both files and link them:

$ gcc main.c another_file.c -o my_program
$ ./my_program

Expected Output:

Initial shared value (declared extern): 42
Current shared value (defined here): 100

The same declaration concept applies to functions.

Expressions in C can be categorized:

  1. lvalue (Locator value): Represents an object that occupies an identifiable location in memory (like a variable). An lvalue can appear on the left-hand side or right-hand side of an assignment.
  2. rvalue (Read value): Represents a temporary value that doesn’t necessarily have a persistent memory location (like a literal number or the result of an expression). An rvalue can appear on the right-hand side but not the left-hand side of an assignment.

Variables are lvalues.

int score = 95; // 'score' is an lvalue, '95' is an rvalue

Literals (like 10, 3.14, 'A') are rvalues.

10 = score; // Invalid: Cannot assign to an rvalue (10)

Understanding this distinction helps grasp assignment rules and operator behavior.

Constants are fixed values that a program cannot change during execution. These fixed values themselves are often called literals.

Literals can represent basic data types: integer literals, floating-point literals, character literals, and string literals. There are also enumeration constants.

Named constants, unlike literals, provide a symbolic name for a fixed value, improving readability and maintainability.

Represent whole numbers. Can be specified in different bases:

  • Decimal: No prefix (e.g., 123, -5).
  • Octal: Prefix 0 (e.g., 017 which is decimal 15). Digits 0-7 allowed.
  • Hexadecimal: Prefix 0x or 0X (e.g., 0xFF which is decimal 255, 0x1a). Digits 0-9, a-f, A-F allowed.

Suffixes can specify the type:

  • U or u: Unsigned (e.g., 100U, 0xFU).
  • L or l: Long (e.g., 12345L, 077L).
  • LL or ll: Long Long (C99) (e.g., 9876543210LL, 1ULL).

Suffixes can be combined (e.g., 100UL, 50LLU). The case and order don’t matter (UL is same as lu).

Examples:

212 // int (decimal)
215u // unsigned int
0xFeeL // long int (hexadecimal)
0123 // octal (decimal 83)
123456789LL // long long int
40000U // unsigned int
// Invalid Examples:
// 078 // Error: 8 is not an octal digit
// 032UU // Error: Cannot repeat U suffix

Represent real numbers with fractional parts. Can have an integer part, a decimal point, a fractional part, and an exponent part (using e or E).

Representation forms:

  • Decimal form: Must include a decimal point, an exponent, or both (e.g., 3.14159, 123., .456, 1e5).
  • Exponential form: Integer/fractional part followed by e or E, then an optionally signed exponent (e.g., 314159E-5L, 1.23e+2).

Suffixes specify the type (default is double):

  • f or F: float (e.g., 3.14f, 1.0F, 2.5e-3f).
  • l or L: long double (e.g., 0.123456789L, 1.2E+10L).

Examples:

3.14159 // double
3.14f // float
123.456L // long double
1.0E6 // double (1 million)
0.0 // double
-1.23e-2f // float (-0.0123)
// Invalid Examples:
// 510E // Incomplete exponent
// 210f // No decimal point or exponent for float (needs 210.0f or similar)
// .e55 // Missing integer or fractional part before exponent

Represent a single character, enclosed in single quotes (e.g., 'a', '%', '7'). They have an integer type (int).

Special characters can be represented using escape sequences:

Escape SequenceMeaning
\\Backslash (\) character
\'Single quote (') character
\"Double quote (") character
\?Question mark (?) character
\aAlert (bell)
\bBackspace
\fForm feed
\nNewline (line feed)
\rCarriage return
\tHorizontal tab
\vVertical tab
\0Null character
\oooOctal number (one to three digits)
\xhh...Hexadecimal number (one or more digits)

Examples:

'A' // Plain character
'\n' // Newline character
'\t' // Tab character
'\'' // Single quote character
'\0' // Null character (integer value 0)
'\x41' // Hexadecimal representation for 'A'
'\101' // Octal representation for 'A'

Represent a sequence of characters enclosed in double quotes (e.g., "Hello, World!"). They are stored as arrays of characters, automatically terminated by a null character (\0).

String literals can contain escape sequences. Long string literals can be broken across lines by ending a line with a backslash \ (the backslash and newline are ignored) or by concatenating adjacent string literals (the compiler combines them).

Examples (all represent the same string “Hello, dear”):

"Hello, dear"
"Hello, \
dear"
"Hello, " "dear"

Example containing escape sequences:

"First line\nSecond line\tIndented"
"Display a backslash: \\ and a double quote: \""

Using named constants makes code more readable and easier to modify. Two primary ways:

  1. Using the #define preprocessor directive.
  2. Using the const keyword.

Creates a macro that the preprocessor replaces with its value before compilation. It’s a simple text substitution.

#define PI 3.14159
#define MAX_USERS 100
#define GREETING "Welcome!"
// Usage:
double circumference = 2 * PI * radius;
if (user_count < MAX_USERS) { /* ... */ }
printf("%s\n", GREETING);

Conventionally, #define constant names are uppercase. Note: #define does not create typed variables; it’s pure text replacement.

Declares a variable whose value cannot be changed after initialization. These are actual variables with types, providing better type safety than #define.

const type variable_name = value;

Examples:

const double PI = 3.14159;
const int MAX_USERS = 100;
const char *GREETING = "Welcome!"; // Pointer to constant string
// Usage:
double circumference = 2 * PI * radius;
if (user_count < MAX_USERS) { /* ... */ }
printf("%s\n", GREETING);
// Attempting to modify a const variable results in a compile-time error:
// PI = 3.14; // ERROR!
// MAX_USERS++; // ERROR!

Best Practice: Prefer const variables over #define for defining named constants whenever possible, as they provide type checking and are integrated better into the language’s scoping rules.

Storage class specifiers in C define the scope (visibility) and lifetime (duration) of variables and functions. They precede the data type in a declaration or definition.

The main storage class specifiers are:

  • auto
  • register
  • static
  • extern

auto is the default storage class for variables declared inside a function or block (local variables). It specifies automatic storage duration.

  • Scope: Limited to the block where they are defined.
  • Lifetime: Created when the block is entered, destroyed when the block is exited.
  • Initialization: Not automatically initialized; hold undefined values unless explicitly initialized.
  • Usage: Rarely used explicitly, as it’s the default for local variables.
{
int count; // Implicitly 'auto int count;'
auto float sum = 0.0f; // Explicit 'auto', same effect
}

register suggests to the compiler that a local variable should be stored in a CPU register for faster access, rather than in RAM.

  • Scope & Lifetime: Same as auto (local to the block, automatic duration).
  • Storage: Hint to use a register; the compiler is not obligated to honor it. Modern compilers often perform better register allocation automatically.
  • Address: Cannot take the address (& operator) of a register variable (as registers don’t have memory addresses).
  • Usage: Largely obsolete. Compilers usually do a better job optimizing. Historically used for frequently accessed variables like loop counters.
{
register int loop_counter; // Hint to use a register
// Cannot do: &loop_counter
}

Because compilers ignore this hint often, its use is generally discouraged in modern C code.

static has two main uses depending on context:

  • Scope: Limited to the block where defined (like auto).
  • Lifetime: Static storage duration. The variable persists for the entire execution of the program. It retains its value between function calls.
  • Initialization: Initialized only once (at compile time or first entry). If not explicitly initialized, defaults to zero (or NULL for pointers).
#include <stdio.h>
void counter_func(void) {
static int call_count = 0; // Initialized only once
call_count++;
printf("Function called %d times.\n", call_count);
}
int main(void) {
counter_func(); // Output: Function called 1 times.
counter_func(); // Output: Function called 2 times.
counter_func(); // Output: Function called 3 times.
return 0;
}

2. Outside a Function (Global Variables and Functions):

Section titled “2. Outside a Function (Global Variables and Functions):”
  • Scope: Restricts the visibility (linkage) of the global variable or function to the current source file (internal linkage). It cannot be accessed directly from other .c files (even using extern).
  • Lifetime: Static storage duration (persists for program execution).
  • Initialization: Same as global variables (initialized to zero/NULL if not explicitly set).
// --- file1.c ---
static int file_local_secret = 42; // Visible only within file1.c
static void internal_helper(void) { // Callable only within file1.c
// ...
}
void public_interface(void) {
internal_helper();
file_local_secret++;
}
// --- file2.c ---
// extern int file_local_secret; // ERROR: file_local_secret has internal linkage
// extern void internal_helper(void); // ERROR: internal_helper has internal linkage
extern void public_interface(void); // OK: public_interface has external linkage (default)
int main(void) {
public_interface();
// Cannot access file_local_secret or internal_helper here
return 0;
}

static is crucial for encapsulation in multi-file projects, hiding implementation details.

extern is used to declare a global variable or function that is defined elsewhere (typically in another source file). It indicates external linkage.

  • Purpose: Provides a reference to a global identifier defined in another module.
  • Scope: Makes the global identifier visible in the current scope.
  • Lifetime: Refers to a variable with static storage duration.
  • Initialization: Cannot initialize an extern declaration (initialization happens at the definition).
// --- config.c ---
#include <stdbool.h>
const char* server_address = "192.168.1.100"; // Definition
bool logging_enabled = true; // Definition
// --- main.c ---
#include <stdio.h>
#include <stdbool.h>
// Declarations of variables defined in config.c
extern const char* server_address;
extern bool logging_enabled;
int main(void) {
printf("Server: %s\n", server_address);
if (logging_enabled) {
printf("Logging is ON\n");
}
return 0;
}

extern is fundamental for sharing global variables and functions across multiple source files in a C project.

Operators are symbols that perform operations on variables and values (operands). C provides a rich set of operators.

Perform mathematical calculations. Assume int A = 10; and int B = 20;

OperatorNameDescriptionExample
+AdditionAdds two operandsA + B gives 30
-SubtractionSubtracts the second operand from the firstA - B gives -10
*MultiplicationMultiplies both operandsA * B gives 200
/DivisionDivides numerator by denominator. Integer division truncates remainder.B / A gives 2. 10 / 3 gives 3.
%ModulusYields the remainder of an integer division.B % A gives 0. 10 % 3 gives 1.
++IncrementIncreases integer value by one. Can be prefix (++A) or postfix (A++).A++ evaluates to 10, then A becomes 11. ++A makes A become 11, then evaluates to 11.
--DecrementDecreases integer value by one. Can be prefix (--A) or postfix (A--).A-- evaluates to 10, then A becomes 9. --A makes A become 9, then evaluates to 9.

Compare two operands and return a boolean result (1 for true, 0 for false). Assume int A = 10; and int B = 20;

OperatorNameDescriptionExample
==Equal toChecks if values are equal.(A == B) is false (0).
!=Not equal toChecks if values are not equal.(A != B) is true (1).
>Greater thanChecks if left operand is greater than right.(A > B) is false (0).
<Less thanChecks if left operand is less than right.(A < B) is true (1).
>=Greater than or equal toChecks if left operand is greater than or equal to right.(A >= B) is false (0).
<=Less than or equal toChecks if left operand is less than or equal to right.(A <= B) is true (1).

Combine or modify boolean expressions. Operands are typically treated as true if non-zero, false if zero. Assume int A = 1; (true) and int B = 0; (false).

OperatorNameDescriptionExample
&&Logical ANDTrue (1) if both operands are true (non-zero).(A && B) is false (0).
||Logical ORTrue (1) if at least one operand is true (non-zero).(A || B) is true (1).
!Logical NOTReverses the logical state: makes true false, and false true.!(A && B) is true (1). !A is false (0).

Logical AND (&&) and OR (||) perform short-circuit evaluation: If the result can be determined from the left operand, the right operand is not evaluated (e.g., in false && expression, expression is never evaluated).

Perform operations directly on the binary (bit) representation of integer operands. Assume unsigned char A = 60; (0011 1100) and unsigned char B = 13; (0000 1101).

OperatorNameDescriptionExample (using 8 bits)
&Bitwise ANDSets each bit to 1 if both corresponding bits are 1.A & B gives 12 (0000 1100).
|Bitwise ORSets each bit to 1 if at least one corresponding bit is 1.A | B gives 61 (0011 1101).
^Bitwise XORSets each bit to 1 if the corresponding bits are different.A ^ B gives 49 (0011 0001).
~Bitwise NOT (Ones’ Complement)Flips all bits (0s become 1s, 1s become 0s). Unary.~A depends on size, for 8 bits: 1100 0011 (decimal 195 if unsigned, -61 if signed 2’s complement).
<<Left ShiftShifts bits left, filling right with 0s. x << n is equivalent to x * 2^n.A << 2 gives 240 (1111 0000).
>>Right ShiftShifts bits right. For unsigned types, fills left with 0s (Logical Shift). For signed types, behavior is implementation-defined (can be logical or arithmetic shift - filling with sign bit). x >> n is approx x / 2^n.A >> 2 gives 15 (0000 1111).

Assign values to variables.

OperatorNameExample
=Simple AssignmentAssigns value from right to left operand.
+=Add ANDC += A is shorthand for C = C + A
-=Subtract ANDC -= A is shorthand for C = C - A
*=Multiply ANDC *= A is shorthand for C = C * A
/=Divide ANDC /= A is shorthand for C = C / A
%=Modulus ANDC %= A is shorthand for C = C % A
<<=Left shift ANDC <<= 2 is shorthand for C = C << 2
>>=Right shift ANDC >>= 2 is shorthand for C = C >> 2
&=Bitwise AND ANDC &= 2 is shorthand for C = C & 2
^=Bitwise XOR ANDC ^= 2 is shorthand for C = C ^ 2
|=Bitwise OR ANDC |= 2 is shorthand for C = C | 2
OperatorNameDescriptionExample
sizeofSizeof operatorReturns the size (in bytes) of a data type or variable. Result type is size_t.sizeof(int), sizeof(myVariable)
&Address-of operatorReturns the memory address of a variable. Unary.&myVariable
*Dereference operator (Indirection)Accesses the value stored at a memory address pointed to by a pointer. Unary.*pointerVariable
? :Ternary conditional operatorShorthand for if-else. condition ? value_if_true : value_if_falsemax = (a > b) ? a : b;
,Comma operatorEvaluates expressions left-to-right, result is the value of the rightmost expression. Used often in for loops.x=1, y=2, z=x+y (z becomes 3)

Precedence determines the order in which operators are evaluated in an expression (like multiplication before addition). Associativity determines the order for operators of the same precedence (e.g., left-to-right for addition/subtraction, right-to-left for assignment).

Highest precedence operators are evaluated first. Use parentheses () to override precedence or clarify order.

CategoryOperatorAssociativity
Postfix() [] -> . ++ --Left to right
Unary+ - ! ~ ++ -- (type)* & sizeofRight to left
Multiplicative* / %Left to right
Additive+ -Left to right
Shift<< >>Left to right
Relational< <= > >=Left to right
Equality== !=Left to right
Bitwise AND&Left to right
Bitwise XOR^Left to right
Bitwise OR|Left to right
Logical AND&&Left to right
Logical OR||Left to right
Conditional?:Right to left
Assignment= += -= *= /= %= >>= <<= &= ^= |=Right to left
Comma,Left to right

Referencing a C operator precedence table online is common for complex expressions.

Decision making allows a program to execute different blocks of code based on whether certain conditions are true or false. This enables programs to respond dynamically to different inputs or states.

The general flow of decision making involves evaluating a condition:

  1. Evaluate a condition (an expression that results in true or false).
  2. If the condition is true, execute a specific block of code.
  3. Optionally, if the condition is false, execute a different block of code (or do nothing).
  4. Continue with the rest of the program.

In C, any non-zero value is considered true, and zero is considered false. Relational and logical operators typically produce 1 for true and 0 for false.

C provides the following decision-making statements:

StatementDescription
if statementExecutes a block of code only if a specified condition is true.
if...else statementExecutes one block of code if the condition is true, and another block if the condition is false.
if...else if...else statementTests multiple conditions in sequence. Executes the block associated with the first true condition, or an optional else block if none are true.
switch statementTests a variable against a list of constant values (cases). Executes the code block corresponding to the matching case.
Nested if statementsUsing one if or else if statement inside another.
Nested switch statementsUsing one switch statement inside another (less common).

We will explore each of these in detail.

Loops are used to execute a block of code repeatedly as long as a certain condition remains true. This avoids writing the same code multiple times.

Programming languages provide control structures for iteration (looping). Without loops, executing a task 100 times would require writing the code 100 times.

The general structure of a loop involves:

  1. Initialization: Setting up loop control variables (optional, often done before the loop).
  2. Condition Check: Evaluating a condition before (or sometimes after) each iteration.
  3. Loop Body: The block of code to be executed repeatedly.
  4. Update: Modifying the loop control variable after each iteration (to eventually make the condition false).

If the condition never becomes false, the loop runs forever (an infinite loop).

C provides the following loop types:

Loop TypeDescription
while loopRepeats the loop body as long as the condition is true. The condition is tested before each iteration.
for loopA compact loop structure often used when the number of iterations is known or easily determined. Includes initialization, condition testing, and update expressions in its header.
do...while loopSimilar to while, but the condition is tested after the loop body executes. Guarantees the loop body runs at least once.
Nested loopsPlacing one loop inside the body of another loop.

These statements alter the normal flow of execution within loops:

Control StatementDescription
break statementImmediately terminates the innermost loop (for, while, do...while) or switch statement containing it. Execution continues with the statement following the terminated loop/switch.
continue statementSkips the rest of the current loop iteration and proceeds to the next iteration’s condition check (and update in for loops).
goto statementTransfers control unconditionally to a labeled statement within the same function. Use of goto is strongly discouraged in modern programming as it makes code hard to read, debug, and maintain. Use break, continue, or function calls instead.

A loop becomes infinite if its condition always evaluates to true. The for loop provides a common idiom for this:

#include <stdio.h>
int main(void) {
// An infinite loop using the for construct
for (;;) { // No initialization, no condition, no update
printf("This loop will run forever... (Press Ctrl+C to stop)\n");
// Usually, some condition inside the loop triggers a 'break'
// Or the program performs a continuous task (like a server)
}
// This part is never reached
return 0;
}

An empty condition in a for loop is implicitly treated as true. While while(1) also creates an infinite loop, for (;;) is a conventional and often preferred way to signify an intentional infinite loop in C.

You typically terminate an infinite loop externally (e.g., pressing Ctrl+C in the terminal) or by using a break statement inside the loop based on some internal condition.