Key Features of the C Programming Language: Code Examples

What do C's features actually do in a program? Follow an even-number sum and a pointer update, then test your understanding with small code changes.

KnowledgeGate Team

Exam prep & CS education

Updated 30 Sep 20266 min read

Lists call C fast, portable and close to hardware, but those labels do not show a beginner what happens in a program. A four-element array, one function and one pointer update make the features easier to understand. We will trace them together, then separate what C provides from shortcuts that cause wrong answers and broken code.

Related reading: C fundamentals and C language concepts.

What are the key features of C?

Each feature answers a practical programming need:

Feature

Practical meaning

Procedural, structured programming

Organise steps using functions, loops and decisions.

Static types

Declare types so the compiler can check how values are used.

Typical native compilation

Build executable code for a target platform.

Pointers

Access objects through their addresses.

Source portability

Reuse standards-based source after checking platform assumptions.

Standard library

Reuse facilities for input, output and other operations.

“Middle-level language” is an informal teaching label for high-level control flow alongside lower-level operations. It is not an official classification. C supports structured programming; the programmer still has to choose a sensible structure.

If you are comparing starting points, the Coding & Skills learning paths provide a wider learning context.

Structured programming and functions: follow one complete program

This program sums the even elements, changes one element, then sums again:

c
#include <stdio.h>

int sum_even(const int *a, int n)
{
    int total = 0;
    for (int i = 0; i < n; ++i) {
        if (a[i] % 2 == 0) {
            total += a[i];
        }
    }
    return total;
}

int main(void)
{
    int values[4] = {3, 4, 7, 10};
    int before = sum_even(values, 4);
    int *p = &values[1];
    *p = 6;
    int after = sum_even(values, 4);
    printf("%d %d\n", before, after);
    return 0;
}

int declares integer values. The array has valid indices 0 through 3. The function receives a pointer parameter a and an integer parameter n, then returns an integer result. const int *a prevents this function from modifying elements through a; it does not make the original array immutable.

The for loop visits each element. The if selects elements whose remainder on division by 2 is zero. Every call starts with a fresh total = 0.

For the first call, index 0 contains 3, so the total stays 0. Index 1 contains 4: 0 + 4 = 4. Index 2 contains 7, so the total stays 4. Index 3 contains 10: 4 + 10 = 14. At i = 4, the condition fails without reading values[4]. The function returns 14 to before.

How the loop sums the even array elements. Show initial total=0 and values={3,4,7,10} above columns i, a[i], action, total after step; rows are 0, 3, skip, 0; 1, 4, add, 4; 2, 7, skip, 4; 3, 10, add, 14; below show i=4 → condition false → return 14.

Compilation, static types and conditional portability

A typical native build follows this route: source → preprocessing and translation → object code → linking → executable for a target. This describes a common toolchain, not a requirement that every C implementation produce a standalone native binary.

Declared types let a compiler check usage. Our guide to variables and data types in C develops that idea further. Here, every worked integer fits comfortably in int, and stdio.h is a standard header. However, a declaration does not automatically protect an array from an invalid index.

Source portability differs from binary portability. You can rebuild suitable source for another target; the same executable need not run on another operating system. Avoid assuming that int always occupies four bytes. C offers opportunities for efficient execution, but language choice alone does not establish which program runs faster.

Pointers expose object access, not magical speed

In int *p = &values[1];, address-of & obtains the second element's address. The declaration makes p a pointer to int. In an expression, dereferencing with *p accesses that element, initially 4.

The assignment *p = 6 changes the array to {3,6,7,10}. On the second call, 3 is skipped, giving total 0; 6 is added, giving 0 + 6 = 6; 7 is skipped, leaving 6; and 10 is added, giving 6 + 10 = 16. The complete output is 14 16 followed by a newline. before remains 14 because it stores the earlier returned integer.

A pointer changes the second element of an array. Draw cells values[0]=3, values[1]=4, values[2]=7, values[3]=10 and a box p pointing to values[1], labelled p=&values[1]; show *p=6 followed by cells values[0]=3, values[1]=6, values[2]=7, values[3]=10 with p still pointing to the second cell; footer labels before=14, after=16, printed output 14 16; use no numeric addresses or byte sizes.

C passes arguments by value, including pointer values. A copied pointer still reaches the caller's object. Use pointer declaration and dereferencing practice to distinguish copying an address from copying an array. Pointers neither grant unrestricted physical-memory access nor automatically improve speed.

Standard libraries and explicit resource management

#include <stdio.h> provides declarations for standard input and output facilities. Here, printf formats two integers using %d. Specialised operating-system facilities require platform-specific interfaces. Functions and separate source files support reuse; C has no built-in classes or inheritance.

The local values array has automatic storage duration. You do not call free on it. A separate allocation could instead begin with int *buf = malloc(4 * sizeof *buf);. This illustrative statement requires <stdlib.h> and requests enough storage for four elements of the pointed-to type.

Check that buf is not null before use, initialise elements before reading them, and call free(buf) when finished. C provides explicit allocation without automatic garbage collection, but not every variable requires manual freeing.

Common traps behind the feature list

“Portable” fails when code assumes integer widths: inspect sizes with sizeof and choose suitable standard types. “Fast” fails when an inefficient algorithm repeats unnecessary work. This example has four loop iterations per call, so 4 + 4 = 8 across both calls. That is an operation count, not a runtime measurement.

Object access follows rules. Reading values[4], dereferencing an uninitialised pointer, or dereferencing a pointer after its allocation is freed invokes undefined behaviour. There is no output to predict. WG14's N1570 C11 committee draft, dated 12 April 2011, describes valid indirection and array-bound pointer arithmetic in clauses 6.5.3.2 and 6.5.6. A one-past pointer may be formed, but not dereferenced.

Use int main(void) for this hosted program, not void main. The local array disproves “all C data lives on the heap”. Passing its starting address disproves “C passes arrays by reference”. “C is always faster” requires measurement, not repetition.

How exams and interviews can test these features

IIT Guwahati's GATE 2026 CS syllabus lists programming in C, recursion and arrays under Programming and Data Structures. That dated syllabus example does not establish fixed marks or question frequency.

These are original practice prompts:

  • What does the complete program print? 14 16: the even sums are 4 + 10 = 14 and 6 + 10 = 16.

  • Remove *p = 6;. What prints? 14 14, because both calls read the unchanged array.

  • Replace it with *p = 5;. What prints? 14 10. On the second call, 3, 5 and 7 are odd; only 10 contributes.

  • Does passing p copy the whole array? No. It passes a pointer value; accessing an element through that pointer is a separate operation.

Trace the object that changes before calculating the next return value.

The short version and your next program

Functions and control flow organise work. Types and typical compilation support implementation. Pointers access objects, portable source respects platform differences, and libraries supply reusable operations.

Now change the original array to {2,5,8,1}, keeping p at index 1 and *p = 6. The first sum is 2 + 8 = 10. After the write, the second is 2 + 6 + 8 = 16. Predict, run and check the output 10 16.

For a structured next step, explore the C Language course and continue practising these ideas in small programs.