Compiler#

Remember that we have to translate our source code to machine code, to do this, we need to install a compiler.

compiler

a computer program that translates computer code written in one language into another language

We will use clang compiler which is part of the LLVM project.

Installation#

  1. Go back to the terminal in your editor.

  2. Install it using:

    brew install llvm
    
    winget install martinstorsjo.llvm-mingw.ucrt
    
  3. Go back to your editor’s terminal.

  4. Check whether clang is installed and available in our environment by entering the following command in the terminal of the editor:

    clang
    

    You should see a message similar as follows:

    clang: error: no input files
    
    Troubleshooting

    If clang does not show the previous message and clang cannot be found, try the following in order:

    1. First find where the compiler is installed. In Windows, the martinstorsjo.llvm-mingw-ucrt package installs the executables including clang under the following path:

    C:\Users\YOUR_USERNAME\AppData\Local\Microsoft\WinGet\Packages\MartinStorsjo*\llvm-mingw-20*\bin
    

    Note the YOUR_USERNAME and * placeholders, so do not copy paste but click through the folder using the file explorer.

    The directory path ending with bin must be part of the path variable in your environment variables. To list and modify your path variable, refer to Editing the environment variable path.

    1. Probably you or your package manager modified your environment variable path, but the editor is not aware of this. Close all editor windows and restart your editor.

    2. Try uninstalling and reinstalling:

      winget remove "LLVM MinGW (UCRT runtime)"
      winget install martinstorsjo.llvm-mingw-ucrt
      

Usage#

Now compile main.c using the following command:

clang main.c

You should get no output, which means your source code is compiled to machine code binary a.exe on Windows and a.out in Linux. You should see it in the EXPLORER column.

Troubleshooting

In Windows, if you get an error during compilation, check whether you have another version of LLVM installed in parallel, for example llvm.llvm in winget. You can list installed llvm packages using:

winget list llvm

If it exists, uninstall the package using its name, e.g.:

winget uninstall llvm.llvm

Alternatively you can also uninstall through Add/remove programs.

Configuring PowerShell for rendering special symbols#

This is only required in Windows.

On Windows, PowerShell is the default terminal shell software. PowerShell uses a different decoding than the usual UTF so useful symbols like çäæ😺 are not rendered correctly. To configure UTF support:

  1. Start a PowerShell, e.g., using CtrlShift`

  2. We will create a profile, which will be executed each time we open a PowerShell window. For security, Windows prohibits executing any scripts. To allow execution of local scripts execute the following on the shell:

    Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
    
  3. The following creates the folders for the profile file and adds the configuration for UTF output.

    mkdir (Split-Path -Parent $PROFILE) -Force
    @"
    [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8
    "@ >> $PROFILE
    
  4. Now restart your editor or close all terminals in your editor.

  5. You can check the setting using:

    [Console]::OutputEncoding
    

    It should output:

    BodyName          : utf-8
    EncodingName      : Unicode (UTF-8)
    ...
    

Running your program#

On the same terminal, type the following to run your program:

./a.out
./a.exe

We need to prefix our program with ./, because your current directory (.) is not part of your path by design.

You should see the output Hello!👋.

Troubleshooting

If you do not see 👋 on the terminal, check Configuring PowerShell for rendering special symbols.

Creating and testing a task#

Typing each time commands to the terminal to compile and run our program is not necessary. The editor features tasks, which can use external programs. We will use tasks to compile our code – or build in general – within the editor and then to run our program.

  1. Try to build your project using CtrlShiftb. You should see No build task ro run found. Configure Build Task....

  2. Click on the last message. You should see Create tasks.json file from template and other menu items related to settings prefixed with the gear icon.

  3. Select Create tasks.json.... You should see different tasks templates including Others | Example to run an arbitrary external command

  4. Click on Others .... .vscode/tasks.json will be created and opened in a new tab. The template defines a task called echo.

  5. Let us try to run the created template. The task echo is not a build task right now, so it cannot be run using the build task.

    To run the build task we could use the traditional menu items on the top. Instead, we will use the command palette, which makes it possible to search for a menu item.

    Press CtrlShiftp. The command palette window should pop up.

  6. Type run task and then select Tasks: Run Task. The window will show many tasks including echo.

  7. Select echo. A new window will show up with Select for which kind of errors ... to scan the task output.

    Scanning the output is helpful, e.g. for:

    1. directly jumping to the error in our source code.

    2. to have an overview of issues in our code on the PROBLEMS tab (left to the TERMINAL tab).

    We are not going to use this feature, as we will directly get feedback about our program syntax mistakes on our editor window through the language server clangd that we will install later.

  8. Click on Continue without scanning the task output. A new terminal window prefixed with 🛠️ echo should be opened and it will output Hello.

    Hello comes from the shell command echo Hello.

    If you don’t get the message, check whether you named your tasks.json correctly. I once named it task.json, which was not easy to spot.

../_images/example-of-cluttered-project-directory.png

Fig. 1 An example of a cluttered documentation project (not C). Here, Markdown (*.md) files are the files I focus on when writing the course materials. Finding an *.md file under week02 is much more convenient than in the bottom where other files are blended in like *.py, *.sh, Makefile etc. Introducing a src folder would separate *.md files.#

Organizing source files under src#

Create a new folder called src in your project folder and move main.c in src folder. From this point on we will put the *.c files in there.

But why? In projects where we have only a single source file, this creates unnecessary directory complexity, so it may seem needless at first. We will introduce later other files like README.md, compile_flags.txt and even other source files (*.c) and header files (*.h) later. We don’t want the mix these files we will mostly use with other meta files, because working with in a project with many source files will be cumbersome as shown in Figure 1.

A project has usually meta files, which are not exactly project contents (in other words source files) but files about the project, e.g., project settings, README, compiler settings, license etc. So we should separate meta files from source files in a project with several files.

Setting up tasks for compiling & running our code using clang#

echo Hello in our current tasks.json does not compile and run our program. For compiling and running we have to tweak tasks.json.

We will set up two different tasks: build and run

  1. Change the tasks.json as follows:

    {
        "tasks": [
            {
                "label": "build",
                "type": "shell",
                "command": "clang",
                "args": [
                    "'@compile_flags.txt'", // `'` for Powershell
                    "./src/main.c",
                    "-o",
                    "main"
                ],
                "group": {
                    "kind": "build",
                    "isDefault": true
                }
            },
            {
                "label": "run",
                "type": "shell",
                "command": "./main",
                "group": {
                    "kind": "test",
                    "isDefault": true
                },
                "dependsOn": [
                    "build"
                ],
            },
        ],
        "version": "2.0.0",
    }
    

    What is going on above?

    • We separated the compiler command from the arguments using args in addition.

      • We need additional arguments to the compiler. Instead of writing all arguments in args, we will introduce compile_flags.txt. @ means that the following argument contains the file where the compiler flags will be read from.

        ' single quotation marks are required to escape @ symbol in PowerShell, because @ has a special meaning.

      The main reason for compile_flags.txt is the language server clangd we will introduce later. Clangd can only read its arguments from compile_flags.txt. We will have the compiler arguments for clang and clangd in one file.

      • We use -o main which sets our program’s name to main instead of ./a.(out|exe).

    • in the run task, we use dependsOn, so that running the run task always builds the project first.

    • we set build as a build task, and run as a test task, because, they are handled differently in VS Code. Moreover we set both tasks as default tasks in their group, which saves an additional click when we want to run these tasks.

  2. Now save tasks.json and create compile_flags.txt with the following content:

    -std=gnu2y
    -g
    -Iinclude
    

    In this file we use:

    • -std option to clang, which stands for C standard.

      Clang uses and older but stable C standard called gnu17 as default. We want to try the latest C features in this course – hence the the option.

    • Added -g option, which generates additional information which can be useful in a debugging session

  3. Run the run task by opening the command pallette (shortcut contains p) and searching for test task. After running you should get an output similar to in the Terminal:

     *  Executing task: clang @compile_flags.txt main.c -o main
    ... 
    

Tip

The build task automatically saves your project files before running the task. Don’t lose time with saving your source files before executing the task.

Appendix#

  • Integrate with external tools via tasks

  • clangd automatically compiles the project and populates Problems tab, so there is no need for problemMatcher in tasks.json. Currently I don’t see any purpose for it, but in case there is, you can set it up as follows:
    "label": "build",
    // ...
    "problemMatcher": {
        "pattern": {
            "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
            "file": 1,
            "line": 2,
            "column": 3,
            "severity": 4,
            "message": 5
        }
    },
    

    A problem matcher is built-in:

    "label": "build",
    // ...
    "problemMatcher": ["$gcc"],
    
  • tasks.json also supports OS specific properties. If you work in a project group or on multiple OSs, you can use the windows, osx, linux:
    {
     "tasks": [
         {
             "label": "run",
             "type": "shell",
             "group": {
                 "kind": "test",
                 "isDefault": true
             },
             "windows": {
                 "command": "./a.exe"
             },
             "osx": {
                 "command": "./a.out"
             },
             "linux": {
                 "command": "./a.out"
             },
             "dependsOn": [
                 "build"
             ],
         },
     ],
     "version": "2.0.0",
    }