Cross-platform C SDK logo

Cross-platform C SDK

Use of C

❮ Back
Next ❯
This page has been automatically translated using the Google Translate API services. We are working on improving texts. Thank you for your understanding and patience.

Most programming languages contain good parts and bad parts. I discovered that I could be a better programmer by using only the good parts and avoiding the bad parts. After all, how can you build something good out of bad parts? Douglas Crockford - JavaScript: The Good Parts.


Programming quickly, reducing the probability of error, ensuring portability and generating optimized binaries have been the main purposes of NAppGUI since its inception and this includes a revision of the C language itself. A subset of ANSI-C (C90) has been used as a basis, with two C99 features: Fixed-size integers <stdint.h> and C++ one-line comments. We recommend that applications based on this SDK follow the same philosophy. Going into more detail, the objectives pursued have been these:

  • Maximum portability: Even on outdated compilers like MSVC 8.0 (Visual Studio 2005) or GCC 4.2 (Xcode 3). The latest language features may not be available on platforms where you must port your code (think embedded devices). You also ensure that said code will be compatible with future versions of the main compilers.
  • Focus attention: On the "what" and not on the "how". Sometimes we make simple things difficult just to justify the use of that new "cool" feature. It is also possible that you are addicted to "being up to date", which will force you to "modernize" the code to adapt it to a new version of the standard. Focus on solving the problem in front of you and, if you can spend more time, on lowering the asymptotic complexity of your solution. NAppGUI will make sure your applications work wherever they are needed.
  • Avoid unremarkable features: Like C11's multi-threaded support (<threads.h>). This is solved with system calls. See Threads.
  • Fast compilation: Certain C constructs are nothing more than a kind of "portable assembler", which the compiler can interpret and translate in an incredibly efficient way.
  • Small and fast binaries: Derived from the previous one, the generated code will require few assembly sentences and will be very easy to optimize by the compiler.

Obviously, this is not the place to learn C, nor is it our pretense. The core of the language is small and easy to remember, but programming well requires years of practice. What we will do here is show the minimum expression of the language that we use daily. Ultimately, these are our standards.


1. Basic types

  • Empty: void.
  • Boolean: bool_t. 8-bit type with only two possible values TRUE (1) and FALSE (0).
  • Integers: uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t. Fixed-size integers were entered into C99 by <stdint.h>. We consider it an advantage to know that our variables will be the same size in all systems. The use of int, long, short or unsigned is prohibited, with the sole exception of comparison functions.
  •  
    
    static int i_cmp_cir(const Cir2Dd *cir1, const Cir2Dd *cir2)
    {
        return (cir1->r < cir2->r) ? 1 : -1;
    }
    
    arrst_sort(circles, i_cmp_cir, Cir2Dd);
    
  • Floating point: real32_t, real64_t. float or double is not used for consistency with integer types.
  • Character: char_t (8 bits). The UTF8 representation is used "de facto" throughout the SDK, so random access to elements of a string is prohibited as it is a variable length encoding. The functions included in Unicode or Strings must be used to manipulate character arrays. The wchar_t, char16_t, char32_t types are not used (or advised). However, if you have wide-char strings, you must convert them to UTF8 before using them in any NAppGUI function.
  •  
    
    // Error! 
    const char_t *mystr = "Ramón tiene un camión";
    while (mystr[i] != '\0')
    {
        if (mystr[i] == 'ó')
        {
            // Do something
        }
        else
        {
            i += 1;
        }
    }
    
    // Correct!
    const char_t *it = mystr;
    uint32_t cp = unicode_to_u32(it, ekUTF8);
    while (cp != '\0')
    {
        if (cp == 'ó')
        {
            // Do something
        }
        else
        {
            it = unicode_next(it, ekUTF8);
            cp = unicode_to_u32(it, ekUTF8);
        }
    }
    
    // Avoid using wchar_t constants (when possible).
    const wchar_t *mywstr = L"Ramón tiene un camión";
    char_t mystr[512];
    
    unicode_convers((const char_t*)mywstr, mystr, ekUTF16, ekUTF8, 512);
    
    // This is a NAppGUI function
    label_text(label, mystr);
    
  • Enums: Their main task is to manage the specialization and they will be evaluated exclusively within a switch. It is forbidden to assign random values ​​to the elements of an enum, except 1 to the first one. 0 is considered not initialized and ENUM_MAX(align_t) as invalid.
  •  
    
    typedef enum _align_t
    {
        ekTOP = 1,
        ekBOTTOM,
        ekLEFT,
        ekRIGHT
    } align_t;
    

2. Structures and unions

 
typedef struct _layout_t Layout;
typedef union _attr_t Attr;

struct _layout_t
{
    Cell *parent;
    Panel *panel;
    bool_t is_row_major_tab;
    ArrSt(Cell) *cells;
    ArrPt(Cell) *cells_dim[2];
    real32_t dim_margin[2];
    color_t bgcolor;
    color_t skcolor;
};

union _attr_t
{
    struct _bool_
    {
        bool_t def;
    } boolt;

    struct _int_
    {
        int64_t def;
        int64_t min;
        int64_t max;
        int64_t incr;
        String *format;
    } intt;

    struct _real32_
    {
        real32_t def;
        real32_t min;
        real32_t max;
        real32_t prec;
        real32_t incr;
        uint32_t dec;
        String *format;
    } real32t;
};

Structure definitions will generally not be public and will remain hidden in the *.c. This means that automatic variables cannot be declared in the Stack Segment and they will only be accessible through opaque dynamic objects.

 
Layout *layout = layout_create(2, 2);
layout_edit(layout, edit, 0, 0);
layout_label(layout, label, 0, 1);
...
panel_layout(panel, layout);

Normally, all dynamic objects will have a destructor. If it does not exist, it is because said object only makes sense as part of another. For example, there is no layout_destroy() or panel_destroy(), but there is window_destroy which will destroy the entire hierarchy of panels and layouts associated to the window.


3. Control

  • if/else. They always open a block {...}, unless ALL paths are made up of a single statement. In general, the use of functions as arguments to the if/else is avoided with the exception of pure functions.
  •  
    
    if (x == 1)
    {
        j += 2;
        i_do_something(j);
    }
    else
    {
        i_do_nothing();
    }
    
    if (bmath_sqrtf(sqlen) < 20.5f)
        i_do_something(j);    
    
  • while. Nothing to comment.
  • do/while. Not allowed. Use for or while.
  • for. For infinite loops, for(;;) is used instead of while(TRUE). Avoid warnings in some compilers. Since there are compilers based on ANSI-C, such as MSVC++ 8.0, we do not use variable declarations inside the for(), a feature that was incorporated in C99.
  •  
    
    // Infinite loop
    for(;;)
    {
        ...
    }
    
    // Will not work in some compilers (not used)
    for (uint32_t i = 0; i < 1024; ++i)
    {
        ...
    }
    
  • switch. It is only used to discriminate between the values ​​of an enum. Another data type will NEVER be evaluated in a switch nor will an enum be discriminated within an if/else construct. The compiler can drastically optimize the performance of a build with these characteristics.
  •  
    
    switch(align) {
    case ekTOP:
        ...
        break;
    
    case ekBOTTOM:
        ...
        break;
    
    case ekLEFT:
        ...
        break;
    
    case ekRIGHT:
        ...
        break;
    
    cassert_default();
    }
    

4. Functions

  • A function can return nothing (void), a basic type or a pointer.
  • Input parameters are always const even though they are simple types passed by value.
  • For any parameter that is not of simple type, a pointer will be passed. Never a structure for value.
  • The number of public functions should be reduced to the maximum, which will be declared in the *.h and defined in the *.c.
  • The supporting (or private) functions will be defined static, inside the *.c module and will not have a declaration.
  • Public function.
     
    
    // layout.h
    void layout_hsize(Layout *layout, const uint32_t col, const real32_t wid);
    
    // layout.c
    void layout_hsize(Layout *layout, const uint32_t col, const real32_t wid)
    {
        i_LineDim *dim = NULL;
        cassert_no_null(layout);
        cassert_msg(wid >= 0.f, "Column 'width' must be positive.");
        dim = arrst_get(layout->lines_dim[0], col, i_LineDim);
        cassert_no_null(dim);
        dim->forced_size = wid;
    }
    
    Private function. It can only be called within layout.c.
     
    
    // layout.c
    static Cell *i_get_cell(Layout *lay, const uint32_t c, const uint32_t r)
    {
        register uint32_t position = UINT32_MAX;
        cassert_no_null(lay);
        cassert(c < arrst_size(lay->lines_dim[0], i_LineDim));
        cassert(r < arrst_size(lay->lines_dim[1], i_LineDim));
        position = r * arrst_size(lay->lines_dim[0], i_LineDim) + c;
        return arrst_get(lay->cells, position, Cell);
    }
    

5. Scopes

Variables are declared at the beginning of a block and cannot be mixed with statements, unless we open a new scope. Declarations mixed with statements is a C++ feature added to the C99 standard, but not all C compilers support it. It is allowed to initialize a variable by calling a function.

 
{
    // Ok!
    uint32_t var1 = 5;
    uint32_t var2 = i_get_value(stm);
    uint32_t var3 = i_get_value(stm);

    i_add_values(var1, var2, var3);

    // Error in C90 compilers
    uint32_t var4 = 6;

    // Ok!
    {
        uint32_t var4 = 6;
        ....
    }
}

6. Pointers

Apart from the advantages of using pointer arithmetic when implementing certain algorithms, in NAppGUI pointers are used essentially in two situations:

Special mention should be made of the function pointers widely used in C, but less so in C++ since the language hides them within the vtable. However, a strategically placed function pointer can make it easy for us to add specialized functionality to existing objects, without having to adopt a more purist object-oriented design.

 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
typedef struct _shape_t Shape;
typedef void(*FPtr_draw)(const Shape*, DCtx *ctx);

struct _shape_t
{
    ArrSt(V2Df) *points;
    Material *material;
    ...
    FPtr_draw func_draw;
};

static void i_draw_conceptual(const Shape *shape, DCtx *ctx)
{
    // Do simple drawing
}

static void i_draw_realistic(const Shape *shape, DCtx *ctx)
{
    // Do complex drawing
}

Shape *shape1 = heap_new(Shape);
Shape *shape2 = heap_new(Shape);
shape1->func_draw = i_draw_conceptual;
shape2->func_draw = i_draw_realistic;

7. Preprocessor

Our standards make heavy use of the preprocessor, especially for compile-time type checking. This helps to detect errors in the code before executing the program (static analysis), unlike the RTTI of C++ that does it once it is running (dynamic analysis).

 
#define arrst_destroy(array, func_remove, type)\
    ((void)((array) == (ArrSt(type)**)(array)),\
    FUNC_CHECK_REMOVE(func_remove, type),\
    array_destroy_imp((Array**)(array), (FPtr_remove)func_remove, (const char_t*)(ARRST#type)))

ArrSt(Product) *products = arrst_create(Product);
...
static void i_remove_product(Product *product)
{

}
...

// 'products' and 'i_remove_product' will be checked at compile time
arrst_destroy(&products, i_remove_product, Product);
Dynamic typing is not necessarily good. You get static errors at run time, which you really should be able to catch at compile time. Rob Pike.

8. Comments

Generally, the use of comments will be reduced as much as possible. A comment will be placed at the beginning of each file as a general description. We also use a comment line as a separator when implementing functions.

stream.c
 
/* Data streams. Manage connection-oriented communication */

#include "stream.h"
#include "stream.inl"
#include "core.inl"
#include "bfile.h"
#include "bmem.h"
...

/*---------------------------------------------------------------------------*/

static void i_func1(void)
{
    // Do something
}

/*---------------------------------------------------------------------------*/

static void i_func2(void)
{
    // Do something
}

C++ comments come in handy to temporarily override lines of code at design time, or entire blocks if we take advantage of the option offered by almost all editors. Doing it with the traditional C comments /* ---- */ can be very tedious, especially if there are already comments within the block that we want to discard.

Another aspect that is totally prohibited is the inclusion of documentation blocks within the source code, not even in the headers themselves. NAppGUI uses ndoc for documentation tasks, a utility that allows you to create html/pdf documents enriched with images, cross references, examples, etc. and that uses its own files totally separated from the code. Another added advantage is the cleanliness of the *.h headers of all modules, where it is very easy to locate what we are looking for.

Documentation blocks are NOT allowed.
 
// Forbidden, non used
/*! Gets the area of ​​the polygon.  
    \param pol The polygon. 
    \return The area.  
*/  
real32_t pol2d_areaf(const Pol2Dd *pol);  
Header example in NAppGUI.
 
/* 2d convex polygon */

#include "geom2d.hxx"

__EXTERN_C

Pol2Df* pol2d_createf(const V2Df *points, const uint32_t n);

Pol2Df* pol2d_copyf(const Pol2Df *pol);

void pol2d_destroyf(Pol2Df **pol);

void pol2d_transformf(Pol2Df *pol, const T2Df *t2d);

const V2Df *pol2d_pointsf(const Pol2Df *pol);

uint32_t pol2d_nf(const Pol2Df *pol);

real32_t pol2d_areaf(const Pol2Df *pol);

bool_t pol2d_ccwf(const Pol2Df *pol);

bool_t pol2d_convexf(const Pol2Df *pol);

__END_C
All comments in NAppGUI are made in English language.

9. Input/Output

The input/output is not part of the C language as such. As the language spread in the mid-1970s, a number of useful routines were grouped into what became the Standard C Library. NAppGUI encapsulates all its functionality in Sewer, Osbs or Core, generally implementing it as calls to the operating system, much more direct and efficient.

 
// Do not use cstdlib in applications
#include <stdio.h>
FILE *fp = fopen("/tmp/test.txt", "w+");
fprintf(fp, "This is testing for fprintf...\n");
fclose(fp);

// Use NAppGUI functions
#include "stream.h"
Stream *stm = stm_to_file("/tmp/test.txt", NULL);
stm_printf(stm, "This is testing for stm_printf...\n");
stm_close(&stm);
The use of the Standard C Library is not recommended. Look for the equivalent function in Sewer, Osbs or Core.
❮ Back
Next ❯