Lammert Bies     Interfacing     Software     Sitemap   
 EN   
Quick links

Hello World in Go vs C

One of the most used first programming examples is Hello World. A source file which does the bare minimum of printing a line with the text Hello World. It is the ideal starting point to begin writing a program if you are not familiar with a programming language because it contains all the components necessary for a program to compile and run.

Hello World implementations from two different worlds

I will show you an example Hello World program for C and for Go, but with a twist. I’ll show you how Go is much more powerful and well-designed than C, an how it adds functionality which has been missing in C for nearly fifty years.

Hello World in C

#include <stdio.h>

// Copyright (C) 2021 - Lammert Bies
// Distributed under terms of the MIT license.

void main( void ) {

        printf( "Hello World\n" );

}  /* main */

That’s pretty simple. The first line tells to include the stdio.h header file which contains the definition of the I/O functions. Then a main() function which is the entry point in most C programs, with in the function one function call to the printf() function which outputs the text to default output device.

Now the Go version

package main

// Copyright (C) 2021 - Lammert Bies
// Distributed under terms of the MIT license.

import "fmt"

func main() {

    fmt.Printf( "Hello World\n" )

}  /* main */

We see that the code is almost identical to the C language version. One extra line is needed for the package name definition and instead of an #include statement for a header file, an import statement for fmt package is used. Comments can be placed between /* and */ just as in C and the starting point of a Go program is also often called main.

Differences between two Similar Software Examples

But let’s focus on the differences, because if Go would just be a replacement of C with a slightly different syntax, there would be no need to switch languages. The first difference is how to let the program run. With C, the common procedure is compiling in two steps. The first step is to compile the human readable source file to an object code file which contains the optimized machine codes. The second step is combining that object code with other relevant pieces of object code by linking it. In some compiler suites the linker is a different program from the compiler, in other suites they are the same, or are at least called through the same meta-compiler but the process is on average the same. And this has been the case since the eighties of the previous century when I was compiling C code on a VAX-11/750 system running BSD and with Turbo-C on a PC.

Executing Go code

Go has two ways to run the code. One is as a script interpreter. In that case there is no need to build an executable. The compiler reads and processes the script and start executing it directly, without first storing it in an object code file. This is how many modern programming languages like for example PHP work. Scripts can be run directly and changed on the fly. This comes in very handy for quick development. If our Hello World example is stored in a text file with the name hello-world.go, the following statement will execute it and print the “Hello World” line on the screen.

go run hello-world.go

But just as with C, it is also possible to compile a source file to a stand-alone executable. This can be achieved with a slghitly different command:

go build hello-world.go

This commands compiles the source to an object file, combines it with all the packages needed for execution and creates the executable file with the name hello-world. The difference with C is that we can do this in a single step. C needs a two-step compilation process because during the first phase of compiling the source file, the C compiler has no idea which libraries or other object files will contain the supplemental functionality to create a working executable. Other object files and libraries have to mentioned explicitly in the call to the linker program, or be set in environment variables or configuration files to make this process work. Compiling a C program is like combining a lot of independent pieces of code.

Package Management

The package system used in Go makes compilation a lot easier. Each package contains a list of packages it relies on. During compile time of the source code, the compiler already knows the complete tree of dependencies and is capable of creating the resulting executable file in just one step.

Even though this is just a small example with just one line of executing code, the differences between the C and Go languages is directly visible after compilation. I compiled both programs on a Centos 8 64 bit Linux system and this output of the ls -l command speaks for itself.

$ ls -l
-rwxr-xr-x 1 lammert develop   12752 Mar 18 21:01 hello-world-c
-rw-r--r-- 1 lammert develop      84 Mar 18 20:58 hello-world-c.c
-rw-r--r-- 1 lammert develop    1504 Mar 18 21:00 hello-world-c.o
-rwxr-xr-x 1 lammert develop 2084989 Mar 18 21:01 hello-world-go
-rw-r--r-- 1 lammert develop      88 Mar 18 20:57 hello-world-go.go

Procedural vs Object Oriented Approach

The hello-world-c executable which was compiled from the C source contains less than 13 thousand bytes. The equivalent Go Version is more than 2 megabytes. Let this sink in for a few seconds because this shows an important difference between the C and Go language. C is a procedural language. What we have written is a procedure containing one statement which prints a text to the standard output device. For that functionality you don’t need a lot of code.

Go on the other hand, is an object oriented language. Object oriented programming is not about the functionality of the code, but about the data it processes. This is an entirely different concept. In essence, our Go source file is not executing a fmt.Printf() statement, it is manipulating the “Hello World\n” string to send it to the output device. Although the programs look almost identical, their concept is 180 degrees different. Procedural languages focus on execution, whereas object oriented languages focus on data.

Although the code effectively does the same, i.e. printing one line of text to an output device, the Go program contains all the functionality needed to completely process data, including the garbage collector and other pieces of code which are not needed for this small program, but which come handy when a program grows bigger. The C code only contains the minimum set of code necessary to perform the text output functionality.

Historical Considerations

There is of course also an age difference. C was developed in the the beginning of the seventies of the previous century, where size of RAM and storage devices was severely limited. The whole compiler build process was designed to minimize the size of programs and that has practically never changed. Compilers even offered the option to optimize for size, rather than speed. Nowadays RAM and permanent storage are in the Gigabyte range or bigger. A few megabytes more or less for a program doesn’t matter anymore.

Powerful string formatting in Go

The avid reader may have seen that I used the fmt.Printf() method to send the text to the output device instead of the fmt.PrintLn() method which is more commonly seen in Go Hello World examples. This is not only because its functionality better resembles the printf() function in C. The reason I did this was to show two things we can do with formatted strings in Go, which are not easy in C.

Unicode formatting

The first functionality is printing Unicode characters. Just as in C, the fmt.Printf() method can print a limited set of special characters by using the backslash followed by a single character. I used this functionality in both examples to print a new line character with ‘\n’. But Go doesn’t limit this to a subset of characters. All characters defined in Unicode are available with \u or \U followed by an for or eight byte long hexadecimal encoded Unicode value. We can replace the call to the fmt.Printf() method in our example with fmt.Printf( “Hello \U0001F310\n” ) That gives us the following output:

Hello 🌐

The word “World” is replaced by the Unicode character U+1F310, which is the representation of our planet. This is not something special in Go. It is supported by all implementations of Go on all systems. Where C was developed in the ASCII area where eight bits were thought to be enough to catch all use cases, Go was built from the ground up with Unicode in mind. All Go source is UTF-8 coded by default which is much better than with C, where Unicode support is spotty at best, and often not working well. Linux implementations of C often prefer UTF-8, whereas Windows is built around UTF-16. This makes writing hardware independent code which runs both flawlessly on Windows and on Linux a horror exercise. To facilitate Unicode encoding, Go provides a rune datatype which can be used to store Unicode encoded characters. In the background it is a 32 bit integer, enough to contain all current and future characters from the Unicode set.

Bit formatting support in Go

Bad native Unicode support is not a surprise in C. But there is another neat feature of the fmt.Printf() method in Go which could, and should, have been implemented in C long ago. C as a programming language has been designed to be used as close as possible to the hardware level, while still being a high level programming language. The smallest data item that we know at the hardware level is the bit, and support for that simple type should have been obvious in C.

But for some strange reason, C never properly implemented bits. Boolean values were represented as integers of any possible length with practically 0 representing false and any other value representing true. Single bit size integer fields in structures are possible, but in practice compilers will pad the length of such structure with extra bits, until the length is reached of the smallest data item representation at the CPU level for that specific hardware environment. C doesn’t even have a way to format bits with the printf() function. Octal values are possible, but single bits are not. This has finally been fixed in the fmt.Printf() method in Go.

For this the format string %b can be used. It shows the bit representation of a value in the same way %x shows the hexadecimal value. Why this hasn’t been implemented in C from the early beginning is beyond my imagination. If your day job focuses around programming at the hardware level as I have been doing the last 30 years, it is a functionality you need almost daily. Let’s see how this works in practice by replacing the fmt.Printf() line in the Go program with fmt.Printf( “%b ways to say Hello World\n”, 5 ) The output of the program will now be:

101 ways to say Hello World

This is enough for the first introduction in the transition from C to the Go language. In other chapters, I will show you more differences and similarities between the languages. Please be aware that this is not a beginners course in programming in general. I will mainly focus on the ways where Go is more powerful or really different from C, and these are often the darker areas of programming. Think of parallel processing, data conversion and all kind of shady edge cases.

  Mar. 2021
   Copyright © 1997-2021 Lammert Bies, All rights reserved