C is a general-purpose programming language used to describe calculations, control flow, and operations on data and memory. You write those instructions as source code, then use a compiler and an execution environment to turn them into a running program. Its compact syntax can express a small classroom exercise or become part of a much larger system.
To understand C, it helps to connect three questions: where did it come from, what does it let a programmer control, and what responsibilities come with that control? This guide follows its origins, explains the people involved, and uses one small program to make the language’s main ideas concrete.
What kind of programming language is C?
C is commonly described as procedural: a program is organized around functions that operate on data. Inside those functions, statements change values, choose between actions, repeat operations, and call other functions. It is also statically typed: declarations provide type information used when the program is translated.
For example, int can represent an integer, double can represent a floating-point value, and a pointer type describes a reference to an object or function of a particular type. These categories affect which operations are allowed and how expressions are interpreted. The same written operator can therefore behave differently depending on its operands.
You may hear C called a “middle-level language.” That is an informal teaching label, not an official level in the C standard. The useful idea is that C provides structured programming constructs while still exposing details such as object representation, addresses, and resource lifetime. You do not need to memorize the label to use the language correctly.
Who created C, and where did it originate?
Dennis M. Ritchie created C at Bell Labs, in the United States. Bell Labs’ archival biography of Ritchie lists 1972 as the year of its creation. That makes 1972 a useful short answer, although the language developed through several iterations rather than appearing fully formed on one day.
| Creator | Dennis M. Ritchie |
|---|---|
| Research setting | Bell Labs, Murray Hill, New Jersey, United States |
| Commonly cited creation year | 1972 |
| Immediate predecessor | B, developed by Ken Thompson |
| Early purpose | System implementation, closely connected with UNIX |
The surrounding contributors had different roles. Ken Thompson created B and was central to UNIX’s development. Brian Kernighan coauthored The C Programming Language with Ritchie; the first edition appeared in 1978. The familiar name “K&R” refers to those authors, not to two people jointly inventing C.
In Ritchie’s own account of C’s development, the broader emergence spans 1969–1973, with especially important work in 1972. That explains why a history can give a range while a quick reference gives a single year.
How BCPL and B led to C
C had predecessors. Martin Richards designed BCPL in 1966 and first implemented it at MIT in spring 1967, according to his first-person BCPL history. BCPL influenced B, and B became the starting point for C.
B’s word-oriented model was awkward for the byte-oriented PDP-11. Ritchie added types and developed a compiler producing machine instructions; an intermediate version was called NB, for “New B.” Bell Labs’ B-to-C archive describes this transition. C was therefore a response to practical language and implementation problems, not merely a new spelling of B.
Why the letter C? Ritchie followed the single-letter naming style after B, while leaving open whether the progression was through the alphabet or the letters of BCPL. C is not an abbreviation for “compiler.” The concise name hides a history of changing types, syntax, and compiler implementation.
The connection to modern programming is useful: an int, a char, and a pointer are different kinds of values. A language that distinguishes them gives its compiler more information than one that treats every value as the same undifferentiated word. Those distinctions also ask the programmer to understand conversions and the meaning of each operation.
Why UNIX mattered to C’s development
Early UNIX was written in assembly language. In 1973, its kernel was rewritten in C, a milestone described in Ritchie’s history of UNIX. This gave the language a demanding real application: implementing an operating system rather than only demonstrating short calculations.
Assembly instructions are tied closely to a processor’s instruction set. Expressing more of a system in C allowed developers to preserve more source while changing the compiler and the platform-specific pieces. This helped make portability a practical concern. It did not eliminate hardware-specific code or make every operating-system interface part of standard C.
For a learner, the distinction is important. Portable source means the program can be adapted or rebuilt for compatible environments. It does not mean one native executable runs unchanged everywhere. A Windows executable, a Linux executable, and a WebAssembly module are different targets even when some of their C source is identical.
The features that define C
Functions and structured control flow
Functions separate work into named operations with parameters and return values. if and switch choose actions; for, while, and do loops repeat work. You can keep a calculation in one function and input handling in another, making each easier to inspect. The functions guide shows how to turn that separation into testable code.
Explicit types and data structures
Arrays group elements of one type, while a struct groups named members that can have different types. An array of readings and a record containing a sensor identifier are different representations. Choosing the representation deliberately helps you explain which values belong together and which operations should act on them.
Pointers and object lifetime
Pointers let code refer to objects through addresses, pass access to data into functions, and work with dynamically allocated storage. A pointer’s type does not prove that its value refers to a live, correctly sized object. Lifetime, bounds, and ownership remain part of the program’s design. Begin with the pointer guide before trying complicated allocation patterns.
A language plus a standard library
C’s syntax and its library are related but distinct. return is a language keyword; printf is a library function declared through stdio.h. Other headers provide facilities for strings, allocation, mathematics, and more. A header makes interfaces available; the build and runtime supply the implementations used by the program.
Portability with conditions
Well-chosen standard features help source move between systems, but assumptions can undermine that portability. Do not assume an int has one fixed width on every implementation, that a particular operating-system header exists everywhere, or that a file’s raw binary representation is universal. Define the needed ranges and interfaces before relying on a platform’s details.
Where is C used?
C remains useful when software must work closely with data representation, existing native interfaces, or constrained execution environments. Two concrete examples show different roles:
- Operating-system code. The Linux kernel documentation describes its C implementation and compiler dialect, while also documenting support for Rust. A large system can use several languages; “uses C” need not mean “uses only C.”
- Embedded libraries. SQLite provides a database engine within an application rather than requiring a separate database server. Its project overview points to the C source distribution. Here, “embedded” describes integration into an application, not necessarily a microcontroller.
Other suitable projects include command-line utilities, parsers for file formats, and firmware components built with a device’s toolchain. These tasks still need libraries and interfaces supplied by their environment. Standard C alone does not provide a desktop window system, a universal networking API, or a driver for every piece of hardware.
For learning, you can start much smaller: summarize a few readings, check a condition, or process an array. The value comes from being able to explain the data and operations, not from making your first program resemble an operating system.
A complete C program you can read and run
This program summarizes three readings. Before running it, calculate 18 + 21 + 24 and divide the result by three. The source makes the data, repetition, and output visible without requiring interactive input yet.
#include <stdio.h>
int main() {
int readings[3] = {18, 21, 24};
int total = 0;
for (int i = 0; i < 3; i++) {
total += readings[i];
}
double average = total / 3.0;
printf("Total: %d\n", total);
printf("Average: %.1f\n", average);
return 0;
}Paste it into the online C compiler and select Run code. The expected program output is:
Total: 63
Average: 21.0Connect the source to its behavior
- The include directive makes the standard output declarations available. It is handled as part of preparing the source.
- main is the starting function for this hosted C program. Returning zero reports a successful exit to the execution environment.
- readings stores three integers. The valid element indexes are 0, 1, and 2.
- The loop visits each of those indexes once. total starts at zero, then becomes 18, 39, and 63.
- The average uses 3.0 so the division is floating-point. %.1f displays one digit after the decimal point; %d displays the integer total.
Change the last reading from 24 to 25. Predict the total of 64 and the displayed average of 21.3, then run again. Next try three zeros. Small controlled changes test your understanding more effectively than copying a much larger program.
This exercise deliberately fixes the element count at three. If you add elements, update both the array size and the loop and averaging logic, or learn how to derive the count with sizeof. The array lesson explains why a count and an array’s capacity must be managed consistently.
For terminal input, continue with printf and scanf. Keep the known-value version as a reference, then add one input request at a time and check whether the conversion succeeded. That separates a calculation error from an input-handling error.
C standards: the language has continued to evolve
C is standardized through ISO/IEC JTC 1/SC 22/WG14. Its official revision list distinguishes familiar version names from the publication identifiers:
| Name | Publication |
|---|---|
| C89 / ANSI C | ANSI X3.159-1989 |
| C90 | ISO/IEC 9899:1990 |
| C99 | ISO/IEC 9899:1999 |
| C11 | ISO/IEC 9899:2011 |
| C17 | ISO/IEC 9899:2018 |
| C23 | ISO/IEC 9899:2024 |
As of this article’s publication, C23 is the latest published revision; ISO lists it as ISO/IEC 9899:2024. Its informal name does not mean the ISO document was published in 2023. Likewise, C17’s publication identifier uses 2018.
A standard specifies language and library rules; it is not a particular compiler installation. Compiler support and the selected language mode both matter. Circuit currently uses C17 with Clang, as described in the environment documentation. A newer standard’s existence does not mean that every older course example is obsolete or that every compiler enables its features by default.
C is not the compiler, the editor, or C++
C is the language. GCC and Clang are toolchains that implement translation for selected targets. VS Code is an editor that can launch those tools. Circuit provides an editor, a browser-based compiler/runtime, and a local program manager. Keeping these roles separate makes setup and troubleshooting easier.
A .c filename conventionally identifies C source, but the extension cannot make invalid text a valid program. The compiler still checks the source. The compilation process walkthrough follows the intermediate files, while our VS Code setup article explains how an editor and a toolchain work together.
C and C++ are related, distinct languages. C++ has facilities such as classes and templates that are not standard C features. Similar-looking source can also have different rules in the two languages. Do not assume a program intended for one becomes correct for the other simply by changing its extension or compiler command.
Describing C as a compiled language summarizes a common workflow, but compilation is an implementation step rather than a feature belonging only to C. Our compiler and interpreter guide explains the difference between translating code and executing its instructions.
C’s strengths come with responsibilities
What you gain
C makes many decisions visible: how data is grouped, which object a pointer refers to, when allocated storage is released, and how an operation changes state. This is useful both in systems work and in learning how software uses memory. Its interfaces also make it possible to work with existing C libraries and platform APIs.
That control can support efficient implementations, but C is not automatically the fastest choice for every problem. Algorithms, data layout, compiler optimization, libraries, and the workload all affect performance. A careful solution in another language can outperform a poorly designed C program.
What you must check
Array indexing does not provide automatic bounds checks as a language guarantee. A pointer can outlive the object it referred to. Signed integer overflow and many invalid memory operations have undefined behavior. A program might appear to work during one run while still violating its language rules.
Not all memory needs manual allocation: local objects can have automatic storage duration. When you do use allocation functions such as malloc, you must handle allocation failure and arrange the corresponding release. Learn that distinction before turning every local variable into dynamically allocated storage.
Successful compilation and a zero exit status are useful observations, not proofs of correctness. Check expected answers, boundary cases, and error paths. Read warnings, keep inputs small while learning, and consult the diagnostic guide rather than suppressing a message you do not understand.
A practical path for learning C
- Understand one complete program. Work through your first C program. Explain the header, main, each statement, and the output before adding more code.
- Connect variables to decisions and repetition. Use small values in an if statement and a loop. Predict the first and last iteration, then compare your prediction with the terminal.
- Group data and separate work. Practise arrays and functions. Keep input/output separate from calculations so each part is easier to test.
- Learn addresses and lifetime deliberately. Begin with a pointer to one live local object. Introduce allocation only when you can explain who owns the storage and how long it must remain valid.
Circuit lets you practise these single-source exercises in your browser with interactive terminal input and local program saves. Its Clang toolchain builds WebAssembly for a WASI runtime; it does not give a browser arbitrary access to your computer’s hardware or native operating-system APIs. Read the workspace documentation when choosing a larger exercise.
You do not need to memorize C’s whole history before writing useful code. Knowing its origins gives context to its design; understanding a small program gives you a starting point for using that design well. Keep the readings example, change one operation, predict its effect, and use the result to decide what to learn next.