C Programming
itProgramming languages
C Programming
C gives you a small language for expressing computation while staying close to memory and machine resources. It does not hide many details that other languages manage for you. You choose data representations, track object lifetimes, and decide how memory is obtained and released.
That control makes C useful for operating systems, embedded software, language runtimes, libraries, and performance-sensitive components. It also creates risk. A program can compile and still access an object outside its lifetime, cross an array boundary, or depend on behavior the C standard does not define.
This course gives you a map of the language. You will learn how source files become a program, how types describe values, how functions organize work, and why pointers connect so many C concepts. You will also learn where C stops making guarantees.
The language, the implementation, and the library
Keep three layers separate:
- The C language defines syntax, types, expressions, statements, functions, and program behavior.
- A C implementation provides a compiler and an execution environment for a particular platform.
- The standard library provides declared facilities for input and output, strings, memory allocation, numeric work, time, and other common tasks.
ISO/IEC 9899:2024 is the current published C standard. It aims to make C programs portable across different systems. It defines the language and library contract, but it does not prescribe one compiler command or executable format.
Compilers also support language versions and extensions. For example, GCC lets you select an ISO dialect such as C17 or C23, or a GNU dialect that includes extensions. Choose a language version explicitly when portability matters. Treat compiler extensions as platform dependencies.
From source text to a running program
A C program commonly spans source files ending in .c and header files ending in .h. Before compilation, the preprocessor handles directives such as #include and conditional compilation. The compiler translates each translation unit. The linker then combines object files and required libraries into a program.
Headers publish declarations that several translation units need to share. Source files usually hold definitions. A declaration tells the compiler that a name and type exist. A definition supplies the function body or reserves storage for an object.
In a hosted environment, execution starts at main. A freestanding environment, such as some embedded targets, can use an implementation-defined startup function and a smaller set of library facilities.
Values, objects, and types
A value is information such as the integer 42. An object is a region of data storage whose contents can represent a value. A type tells the implementation how to interpret a value and which operations are valid.
C provides arithmetic types, including integer and floating-point types. It also lets you derive array, pointer, and function types. Structures group named members. Unions let members share storage. Enumerations define named integer constants.
Do not assume that every integer type has the same size on every target. The implementation documents choices that the standard leaves open. Use sizeof when code needs an object's size. Use types from stdint.h when an exact-width type exists and the interface truly needs one.
Conversions occur in assignments, arithmetic, calls, and returns. Some conversions are safe for every input. Others can lose range, precision, or sign. Compiler warnings help expose suspicious conversions, but the type design remains your responsibility.
Expressions and control flow
Expressions compute values and can also change program state. Operators cover arithmetic, comparison, logic, bit manipulation, assignment, member access, indexing, and pointer operations.
Statements control execution. Use if and switch for selection. Use for, while, and do for repetition. Use break to leave a loop or switch. Use continue to begin the next loop iteration. Use return to finish a function.
Operator precedence controls grouping when parentheses are absent. Evaluation order is a different rule. Do not pack several dependent state changes into one expression. Split the work into statements when order matters.
Functions and interfaces
A function has a return type, a name, parameter types, and a body. A function declaration with parameter types gives callers a checked interface. A function definition supplies the implementation.
C passes arguments by value. A function receives its own parameter values. To let a function modify a caller's object, pass a pointer to that object. This pattern makes mutation possible, but it also makes ownership and valid lifetime part of the interface.
Design small interfaces that state their contracts. For a pointer parameter, document whether it may be null, what it points to, how many elements are available, and whether the function may modify them. For allocated memory, document who releases it.
Arrays, strings, and pointers
An array is a sequence of elements with one element type. Array indexing starts at zero. The language does not automatically check every index at run time. Your program must keep each access within the array's valid range.
A pointer value refers to an object or function of a referenced type. The address operator obtains a pointer to an object. The indirection operator accesses the referenced object. A null pointer does not point to an object or function and must not be dereferenced.
Arrays and pointers are related but not identical. In many expressions, an array expression is converted to a pointer to its first element. The array still has a fixed extent in its own scope. After conversion, the pointer alone does not carry that extent.
A C string is a contiguous character sequence that includes a terminating null character. The string's length excludes that terminator, but storage must include it. Many library string functions depend on finding it. Missing terminators and undersized destinations turn small mistakes into out-of-bounds access.
Lifetime and memory management
Every object has a storage duration that determines its lifetime. Common local objects have automatic storage duration. Their lifetime normally ends when execution leaves their block. Objects declared with static storage duration live for the entire program. Dynamically allocated objects live from successful allocation until deallocation.
The standard library provides allocation functions such as malloc, calloc, realloc, and free. An allocation request can fail, so check the returned pointer before use. Release each live allocation once when it is no longer needed. Do not use a pointer after the referenced object's lifetime ends.
Memory management is broader than matching allocation and deallocation. You must also preserve bounds, alignment, initialization, and a clear ownership rule. A pointer can be non-null and still be invalid for the operation you attempt.
Defined, implementation-defined, and undefined behavior
Portable C depends on knowing what kind of promise applies:
- Defined behavior: the standard states what the program does.
- Implementation-defined behavior: the implementation chooses and documents one behavior.
- Unspecified behavior: the standard permits multiple possibilities and does not require the implementation to document its choice.
- Undefined behavior: the standard imposes no requirements after the invalid construct or data is used.
Examples of undefined behavior include dereferencing a null pointer and referring to an object after its lifetime. Signed integer overflow is another important case. Do not treat undefined behavior as a predictable error value or guaranteed crash.
Warnings, tests, and sanitizers address different parts of this risk. Strong warnings catch suspicious source patterns. Tests exercise selected inputs. AddressSanitizer can detect memory errors during instrumented runs. UndefinedBehaviorSanitizer can detect several kinds of undefined behavior during instrumented runs. None of these tools proves that arbitrary C code is correct.
Where C fits
C is a strong choice when you need a stable systems interface, direct representation of data, a small runtime footprint, or access to platform facilities exposed through C APIs. It is also common at boundaries because many other languages can call C-compatible interfaces.
C is a poor default when automatic memory safety, rich application libraries, rapid feature development, or strong high-level abstractions matter more than low-level control. You can build those properties in C, but the language does not provide them automatically.
Learn C with a deliberate feedback loop. Select a language standard. Enable warnings. Keep interfaces narrow. Test edge cases. Run instrumented builds. Review every pointer together with its referenced object's bounds, lifetime, and ownership.
