(guest@joequery.me)~ $ |

Learning C (Part 1): The basics

(This post is part of the learning-c series.)

This post kicks off a series of articles designed to teach myself C. As I have decent programming fundamentals, these articles will not cover important, introductory concepts such as good variable names, the purpose of control statements, how conditionals work, etc. If you consider yourself at least a somewhat competent programmer and would like to learn C without the fluff, I invite you to follow along my journey!

Why learn C?

C powers many extremely important projects. A tiny sample of these projects yields some very recognizable names:

If you want to contribute to projects such as these, which are truly changing the world, learning C would be a good first step!

Additionally, C is the goto language for Embedded Systems Programming.

A very, very brief overview of the language

This article in the series will quickly glance over many concepts of C, including iteration, strings, arrays, and more.

Getting started: Compiling and executing a program

Installing a compiler

I will be using gcc as my compiler on an Ubuntu linux VM. You are free to use other compilers while following along with the examples. I will do my best to note when compiler-specific behavior is being discussed.

If you are on Ubuntu and would like to see if gcc is available on your machine, execute

$ gcc -v

You should see results similar to

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.7/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v
--with-pkgversion='Ubuntu/Linaro 4.7.2-2ubuntu1'
--with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs
--enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr
--program-suffix=-4.7 --enable-shared --enable-linker-build-id
--with-system-zlib --libexecdir=/usr/lib --without-included-gettext
--enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7
--libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu
--enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object
--enable-plugin --enable-objc-gc --disable-werror --with-arch-32=i686
--with-tune=generic --enable-checking=release --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc
version 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1)

If you instead get the error

gcc: command not found

Install gcc on the machine

sudo apt-get install gcc

Run gcc -v afterwards to ensure the installation procedure was successful.

Hello, world!

And now, we pay tribute to the classic "Hello, World!". To ensure our environment is working correctly, we will create a trivial program that simply prints "Hello, world!".

Create a file called "hello_world.c".

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

raw source

We will now compile the file.

$ gcc hello_world.c -o hello_world.o

(The above hello_world.c file has been verified to not have syntax errors, so if you encounter a compiler error after executing the command, ensure you have input the file correctly.)

The above gcc command will create a hello_world.o output file, which we can directly execute. To do so, we run

$ ./hello_world.o

And we observe the following output in our shell:

Hello world!

So this very basic usage of gcc can be generalized as follows:

$ gcc some_c_file.c -o some_output_executable.o

If some_c_file.c compiles successfully, we will be able to execute the program via

$ ./some_output_executable.o

The specifics of how the hello_world.c works will be discussed later, for now you should celebrate that you can now compile C files on your machine!

Article content

Structure of a C program

  • The entry point of a program is the main() function.
    • The main() function should be of type int and return 0 upon success.

Variables and expressions

Variables must be declared, with their type preceding, before they are used. Consider the following example, which will fail to compile.

1
2
3
4
5
6
#include <stdio.h>
int main(){
    x = 10; // the type was omitted
    printf("Hello!\n");
    return 0;
}

raw source

The compiler output the following error:

ex1.c: In function 'main':
ex1.c:3:5: error: 'x' undeclared (first use in this function)

Declaring the x as int would compile successfully.

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

raw source

Here is an example of the formatters for printf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
int main(){
    int x = 10;
    float y = 10.0; // float for decimals
    char *mystr = "Hahah";

    printf("x: %d\n", x);
    printf("y: %f\n", y);
    printf("mystr: %s\n", mystr);

    // padding
    printf("x: %5d\n", x);

    // Limit the number of decimal points
    printf("y: %.2f\n", y);

    // Padding AND limit decimal points
    printf("y: %10.2f\n", y);
    return 0;
}

raw source

Click to show output ⇓

x: 10
y: 10.000000
mystr: Hahah
x:    10
y: 10.00
y:      10.00

Data types

  • char - a character, a single byte
  • short - short integer
  • long - long integer
  • double - double-precision floating point
  • float - floating point, may be smaller than double

using #define

#define identifier replacementtext will replace instances of identifier in your program with replacementtext (as long identifier is not between double quotes).

1
2
3
4
5
6
7
8
#include <stdio.h>
#define MYNAME "Joseph"
#define YOURNAME "Dork"
int main(){
    printf("Hello YOURNAME, My name is MYNAME\n");
    printf("Hello %s, My name is %s\n", YOURNAME, MYNAME);
    return 0;
}

raw source

Click to show output ⇓

Hello YOURNAME, My name is MYNAME
Hello Dork, My name is Joseph

Using getchar

getchar() reads text from the input stream. It returns the character read. However, the EOF (end of file) character can be outside the range of the char data type, so you should use int to store the return value of getchar().

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main(){
    int c;
    printf("Type something! Press enter to echo, CTRL-D to exit\n");
    while ((c = getchar()) != EOF){
        putchar(c);
    }
    return 0;
}

raw source

(CTRL-D is the default EOF character for Unix like systems)

I'm getting bored, so here's stuff I find interesting.

Reading a line into a char array.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#define BUF_SIZE 100

void readline(char buf[], int bufsize);

int main(){
    char str[BUF_SIZE];
    readline(str, BUF_SIZE);
    printf("%s\n", str);
    return 0;
}

void readline(char buf[], int bufsize){
    int i,c;
    for(i=0; i<bufsize-1 && (c = getchar()) != EOF && c != '\n'; i++){
        buf[i] = c;
    }
    if(c == '\n')
        buf[i++] = c;
    buf[i] = '\0';
}

raw source

Copying one char array to another.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

void copy(char *fromstr, char *tostr);
int main(){
    char buf[10];
    char str[] = "hello";
    copy(str, buf);

    // prints "hello"
    printf("%s\n", buf);
    return 0;
}

void copy(char *fromstr, char *tostr){
    // This works because A) the null char returns 0, which causes the condition
    // to terminate through B) the fact that assignments return the assign
    // value.
    while(*tostr++ = *fromstr++);
}

raw source

External variables

External variables can be accessed by any name in any functions. External variables must be defined once and only once, while they must also be declared wherever they will be used. They retain their values after functions have returned, in contrast to the standard automatic variables.

External variables cannot be defined and declared at the same time.

1
2
3
4
5
6
#include <stdio.h>
int main(){
    // This will cause a compiler error
    extern int somevalue = 10;
    return 0;
}

raw source | show output ⇓

ex8.c: In function main:
ex8.c:3:16: error: somevalue has both extern and initializer

An example of extern usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
void alter_somevalue();
void alter_somevalue2();
int somevalue = 10;
int main(){
    printf("%d\n", somevalue);
    alter_somevalue();
    printf("%d\n", somevalue);
    alter_somevalue2();
    printf("%d\n", somevalue);
    return 0;
}

void alter_somevalue(){
    extern int somevalue;
    somevalue = 15;
}
void alter_somevalue2(){
    // extern is actually redundant in this case!
    somevalue = 25;
}

raw source | show output ⇓

10
15
25

So when is extern useful? Mainly when dealing with multiple files. We'll get to that later!

Joseph, this "tutorial" was useless!

Sorry! I already knew about most of these concepts. Perhaps the upcoming tutorials will be more interesting!

Tagged as c

(This post is part of the learning-c series.)

Date published - November 19, 2013