Post

Let me C- Intro

C’s philosophy - “Don’t do anything the programmer didn’t ask for”

I am reading the book The C Programming Language, 2nd Edition by K&R. As I was going through the preface of the book, I liked this sentence and want to quote it: ‘C wears well as one’s experience with it grows.’ I had experience with C during my Uni days while I was pursuing my electrical engineering degree. I didn’t realize its importance at that point, but I am now realizing the depth of its presence. I am starting fresh from zero - let’s C how far I can C.

1
2
3
4
5
#include <stdio.h>
main()
{
    printf("Hello World\n");
}

To compile and run the script

1
$gcc helloworld.c -o helloworld && ./helloworld

As soon as I compiled it, it threw an error.

error

It turns out older C standards (K&R C /C89) allowed implicit init, meaning a function with no return type was assumed to return init. GCC now warns about this(and newer standards like C99/C11 removed it entirely).

1
$gcc -std=c89 helloworld.c -o helloworld

Choosing the C89 standard allowed me to run the code. But the right fix is adding init and it is the correct modern way.

1
2
3
4
5
6
#include <stdio.h>
int main()  // ✅
{
    printf("Hello, World!\n");
    return 0;
}

OR

1
2
3
4
5
6
#include <stdio.h>
int main(void)
{
    printf("Hello, World!\n");
    return 0;
}

Under the hood

There are three layers to it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
─────────────────────────────────────────
  YOUR CODE  (helloworld.c)
  #include <stdio.h>
─────────────────────────────────────────
        │ uses declarations from
        ▼
─────────────────────────────────────────
  HEADER FILES  (/usr/include/stdio.h)
  Just function signatures & macros
  Written by glibc developers
─────────────────────────────────────────
        │ actual code lives in
        ▼
─────────────────────────────────────────
  GLIBC  (/usr/lib64/libc.so.6)
  The real compiled implementation
  of printf, scanf, malloc, etc.
─────────────────────────────────────────
        │ talks to
        ▼
─────────────────────────────────────────
  LINUX KERNEL
  Does the real I/O, memory, etc.
─────────────────────────────────────────

The C Standard Library

It is a collection of pre-written functions that come bundled with your C compiler. Instead of writing everything from scratch, you can use these ready-made functions for common tasks like input/output, math, string manipulation, memory management, etc.

#include is a preprocessor directive- it tells compiler -“before compiling, copy the content of this file into my code”

standard library

Here is a snippet of the stdio.h file: stdio

glibc = GNU C Library

It is the actual implementation of the C standard library made by the GNU project. It’s the engine behind all those functions you use.

GNU libc

What is a symbol table? - When C source code gets compiled into a binary (.so file), the human-readable function names don’t disappear completely — they leave behind symbols so the system can find and link them at runtime.

What we are seeing is:

1
2
3
4
5
000000000001b110 T a64l
│                │  │
│                │  └── Function name (symbol)
│                └── Symbol type
└── Memory address (where it lives in the file)

When the program calls printf:

1
2
3
4
5
6
7
8
9
10
11
12
13
Our code says "call printf"
        │
        ▼
Linux loader opens /usr/lib64/libc.so.6
        │
        ▼
Looks up "printf" in the symbol table
        │
        ▼
Finds its memory address
        │
        ▼
Jumps to that address and executes the code

Chapter 1 : A Tutorial Introduction

Example 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

/*Implicit way*/
/*
main(){
    int c;
    c=getchar();
    while(c!=EOF){
        putchar(c);
        c=getchar();
    }
}
*/

/*Explict Way*/

main(){
    int c;
    int result;
    while ((c=getchar()) != EOF){
        result=(c!=EOF);
        putchar(c);
        printf("The value of while statement is %d\n", result);
    }
    result=(c!=EOF);
    printf("The value of while statement is now %d\n", result);
    
}
  • EOF- end of file- its value is defined as -1 in

  • getchar() -> Reads one character at a time from the input stream and returns it as an int. Returns EOF when input is exhausted. Always store into int, never char, to safely handle EOF.

  • putchar() -> Writes one character at a time to the output stream. Takes an int, prints it as a character.

Key Rules

  • Every getchar() call consumes one character — permanently. Calling it twice per loop eats characters silently.

  • Enter (\n) is just a regular character — not EOF.

  • EOF is a condition, not a character — triggered by Ctrl+D on Linux/Mac or end of a piped input.

  • The idiomatic form while ((c = getchar()) != EOF) is preferred — one getchar(), no duplication, no bugs.

Example 2

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(){
    long character_count;
    while(getchar() !=EOF){
        ++character_count;
    }
    printf("The total characters in the file or input is %ld", character_count);
}
1
2
3
4
5
6
7
8
# Compile and Run
(main)> gcc 1-5-2-Character-Counting.c -o 1-5-2-Character-Counting 
(main)> ./1-5-2-Character-Counting

# Output
hello
world
The total characters in the file or input is 12⏎    

OR

1
2
3
4
5
6
7
#include <stdio.h>
int main(){
    double character_count;
    for(character_count=0; getchar()!=EOF; ++character_count)
    ;
    printf(" The total character in the file or input is %.0f", character_count);
}
1
2
3
4
# Output

(main)> echo Hello\nWorld| ./1-5-2-Character-Counting
 The total character in the file or input is 12⏎    

Key Points

  • ++nc — increment operator, cleaner shorthand for nc = nc + 1. Has a mirror –nc for decrementing.

  • long type — used instead of int because input could exceed 32,767 characters (the max for a 16-bit int). Always use %ld with printf for long.

Example 3 Array

The below code is available here as well Array Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main()
{
    int c, i, nwhite, nother;
    int ndigit[10];

    nwhite = nother = 0;
    for (i = 0; i < 10; ++i)
        ndigit[i] = 0;

    while ((c = getchar()) != EOF)
        if (c >= '0' && c <= '9')
            ++ndigit[c-'0'];
        else if (c == ' ' || c == '\n' || c == '\t')
            ++nwhite;
        else
            ++nother;

    printf("digits =");
    for (i = 0; i < 10; ++i)
        printf(" %d", ndigit[i]);
    printf(", white space = %d, other = %d\n", nwhite, nother);
}

key points

1
int ndigit[10];
  • Declares an array of 10 integers - one slot for each digit 0 through 9. Array indices always start at 0 in C.
1
2
for (i = 0; i < 10; ++i)
    ndigit[i] = 0;
  • Arrays are not automatically zero in C - you must initialize them manually.
  • In C, always initialize your variables and arrays before using them. Never assume memory is clean.
1
++ndigit[c - '0'];
  • Since digit characters ‘0’ through ‘9’ have consecutive ASCII values (48–57), subtracting ‘0’ gives you the actual numeric value

Here it is:

cASCII valuec - '0'Array slot
'0'480ndigit[0]
'3'513ndigit[3]
'9'579ndigit[9]

With this in mind, we will look at arrays more in Character Array topic, which is more interesting.

Example 4 Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
int power(int m, int n);  /* function prototype */

/* test power function */
int main()
{
    int i;
    for (i = 0; i < 10; ++i)
        printf("%d %d %d\n", i, power(2,i), power(-3,i));
    return 0;
}

/* power: raise base to n-th power; n >= 0 */
int power(int base, int n)
{
    int i, p;
    p = 1;
    for (i = 1; i <= n; ++i)
        p = p * base;
    return p;
}

Key Points

  • int power(int m, int n); /* declared BEFORE main */ tells the compiler what to expect before it sees the actual function definition
  • int power(int, int); /* names omitted — still valid */

Example 5 Character Arrays

Before diving into the details, let’s look at how it is different from python.

1
2
name="satish"
print(name) #just works

In C, printf has no idea where your string ends — it just walks memory until it hits ‘\0’:

1
2
3
4
5
char name[7] = "Satish";   // '\0' added automatically by the string literal
printf("%s", name);         // walks until '\0' — prints "Satish"

char name2[6] = {'S','a','t','i','s','h'};  // no '\0' !
printf("%s", name2);   // prints "Satish" then keeps going into garbage memory 

Now lets start with this basic example to have a foundational understanding of character array before moving on to the example of book.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main(){
    char word[10];
    int c, i;
    i=0;
    printf("Enter the word:");
    while((c=getchar())!='\n' && c!=EOF)
        word[i++]=c;
    word[i]='\0';
    printf("The word you entered is %s\n", word);
    printf("The length of the word is %d",i);
    return 0;
}
1
2
3
4
5
6
#Output

$ ./1-9-Character-Arrays-Foundation 
Enter the word:Satish
The word you entered is Satish
The length of the word is 6⏎    

key points

  • word[i++] is two operations in one:

    • word[i]=c -> store the character at current position
    • i++ -> then moves to the next slot
  • The ++ is postfix -> it uses i first, then increments.
  • The null terminator word[i]='\0', without it printf("%s", word) would print the word and then keep reading garbage memory until it accidentally hits a zero byte somewhere.
  • word[10] -> This array only has 10 slots (indices 0–9). If the user types more than 9 characters, i reaches 9 and you’d write word[10] — one past the end. This is called a buffer overflow — one of the most dangerous bugs in C.

Book Example

K&R wrote this book in 1988. Back then getline didn’t exist in the standard library. Modern Linux’s now has its own getline with a different signature. The compiler sees two different functions with the same name - and complains. So in example `getline[]` is replaced with `my_getline[]`.

Example Flow:

1
2
3
4
5
6
7
Input: "hi\nlongest line\nok\n"

Iteration 1: my_getline → "hi\n"        len=3  → max=3  → copy to longest
Iteration 2: my_getline → "longest\n"   len=9  → max=9  → copy to longest
Iteration 3: my_getline → "ok\n"        len=3  → 3<9    → skip
EOF:          my_getline → returns 0    → loop exits
                                        → print "longest line"

Example 6 External Variable and Scope

Here is the book example

1
2
3
4
5
6
7
int max;                /* external — every function can see this */
char line[MAXLINE];     /* external — every function can see this */
char longest[MAXLINE];  /* external — every function can see this */

int main() { ... }
int getline(void) { ... }
void copy(void) { ... }

Definition vs Declaration

Definition — creates the variable, allocates memory. Happens exactly once:

1
2
int max;              /* DEFINITION — memory allocated */
char line[MAXLINE];   /* DEFINITION — memory allocated */

Declaration — announces the variable exists somewhere. No memory allocated:

1
2
extern int max;        /* DECLARATION — no memory allocated */
extern char longest[]; /* DECLARATION — no memory allocated */

When to use extern extern is redundant when the variable is defined earlier in the same file:

1
2
3
4
5
6
int max;        /* defined at top */

int main() {
    extern int max;   /* redundant — compiler already saw it */
    max = 0;          /* works either way */
}

Common practice — define all external variables at the top, omit extern:

1
2
3
4
5
6
7
int max;
char line[MAXLINE];
char longest[MAXLINE];

int main() {
    max = 0;    /* no extern needed */
}

extern becomes essential across multiple files:

1
2
3
4
file1.c                    file2.c
───────────────────        ──────────────────
int max;                   extern int max;    ← required
char line[MAXLINE];        extern char line[];

Key Points:

  • Local variables are private, temporary, and passed explicitly — clean and safe.
  • External variables are global, permanent, and visible everywhere — convenient but risky.
  • Definition allocates memory (once). Declaration (extern) just announces the variable.
  • extern can be omitted when the definition appears earlier in the same file.
  • Use external variables sparingly — hidden data flow leads to bugs and unmaintainable code.

Now with this foundational knowledge, we will expand our understanding of additional capabilities of C in another post. Stay tunned.

This post is licensed under CC BY 4.0 by the author.