[C] Hidden features of C - Programming On Unix

Users browsing this thread: 5 Guest(s)
bottomy
Registered
Don't really like calling these things "hidden" features, because they aren't hidden at all. Just most people don't read the standard or what language extensions and builtins their compiler offers.

Anyway here's a few things that I don't see used much, that weren't mentioned in that link. Some of these are new features added to C in C11, and could be achieved using compiler extensions however they're particularly useful having them as apart of the standard.

The first is alignment (added in C11). In this example, it uses alignment to make sure that the types are stored at addresses that have (at least) their lowest bit unused. So it can then use that space to store a flag.
Code:
#include <stdio.h>
#include <stdint.h>

typedef _Alignas(2) float float_t;
typedef _Alignas(2) char char_t;
typedef uintptr_t generic_t;

void PrintGeneric(generic_t);


int main(int argc, char *argv[])
{
    generic_t a = (generic_t)&(char_t){ 'e' }, b = (generic_t)&(float_t){ 3.14f } | 1;
    
    PrintGeneric(a);
    PrintGeneric(b);
    
    
    return 0;
}

void PrintGeneric(generic_t GenericValue)
{
    if (GenericValue & 1) printf("%.2f\n", *(float_t*)(GenericValue & ~1));
    else  printf("%c\n", *(char_t*)(GenericValue & ~1));
}


The next is generic selection (added in C11). The example uses it as a way of getting the format specifier for the current type.
Code:
#include <stdio.h>

#define FORMAT_SPECIFIER(x) \
_Generic((x), \
int: "%d", unsigned int: "%u", \
long: "%ld", unsigned long: "%lu", \
long long: "%lld", unsigned long long: "%llu", \
float: "%f", \
double: "%f", \
long double: "%Lf",\
char *: "%s", char [sizeof(x)]: "%s", \
default: "" \
)

int main(int argc, char *argv[])
{
    printf(FORMAT_SPECIFIER("blah"), "blah");
    return 0;
}

Now that isn't a particularly useful example of generics. One thing I found it really useful for was when I made a modifiable constant value system for my game engine (so constant values could be modified at runtime in an editor, really useful when you know you want a constant value but unsure what value specifically). It would then let me simply do RUNTIME_CONSTANT(value). e.g. int a = RUNTIME_CONSTANT(34);.


The next thing is format specifiers for fscanf (or variants of); the same can also be said for the format specifiers of fprintf (or variants of) but it has differing specifiers and can't be bothered with an example. There's a few things to cover here. One thing you'll often hear when regarding fscanf is that it is not secure when dealing with strings (and as a result many avoid it), but it's perfectly safe to use for strings when done properly. The other thing is that it is more flexible than most assume/know about, so it can be used for more complex input though there is a limit (it's still rather restrictive compared to other string processing solutions that other languages support, and it can also be less efficient to use fscanf over handling the converting yourself as it needs to parse the format string).

A fscanf conversion format specifier (something that begins with %) can set whether it should be ignored (won't be assigned to one of the arguments), a maximum field width (can limit the number of characters to be included in the conversion), a length modifier, and the conversion specifier. Some of the less common conversion specifiers are scansets (%[], or %[^], the latter producing a scanlist of anything but those between the brackets; these can be useful when you want only particular input or you want to include isspace characters), as well as some specifiers specific to some types you may use (size_t, u/intmax_t, ptrdiff_t, stdint types while they don't have their own specifier they do define macros with the appropriate specifier for their type).

The first part of the example uses a scanset to accept only numbers, either up to 15 characters (as determined by the maximum field width) or a non-number. While the next part of the example uses the maximum field width to limit the sizes of the numbers we want, as they're packed tightly together.
Code:
#include <stdio.h>

int main(int argc, char *argv[])
{
    char a[16];
    scanf("%15[0123456789]", a); //could alternatively be %15[0-9] though it's implementation defined
    printf("%s\n", a);
    
    int b, c, d, e;
    //assume packing is as follows: [3 digit number][1 digit number][4 digit number][2 digit number]
    sscanf("2179444442", "%3d%1d%4d%2d", &b, &c, &d, &e);
    printf("%d, %d, %d, %d\n", b, c, d, e);
    
    return 0;
}


A final example (can't be bothered doing anymore) is of floating point environment access. With it you're able to set different options for how floating point values and operations should be handled (this can be stuff from changing what should cause exceptions, to changing how the values should behave, e.g. how it should be rounded). It's mostly implementation defined, and this example will not be portable. The example assumes that you're compiling on x86 architecture that supports SSE and platform that provides FE_DFL_DISABLE_SSE_DENORMS_ENV functionality, and assumes the it will use SSE over x87 for the floating point operations.

In the example it uses the environment access to disable denormals for SSE (which will cause all denormal values to be treated as 0.0). The alternatives for doing this would normally be either using asm and setting the bits in the MXCSR register or using intrinsics or builtins.
Code:
#include <stdio.h>
#include <float.h>
#include <math.h>
#include <fenv.h>
#pragma STDC FENV_ACCESS ON

_Bool isDenormal(float a);
float GetDenormal(void);

int main(int argc, char *argv[])
{
    float Value = GetDenormal();
    
    if (isDenormal(Value)) printf("is denormal: ");
    printf("%e %.1f\n", Value, FLT_MAX * Value);
    
    fenv_t Env;
    fegetenv(&Env);
    fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
    
    Value = GetDenormal();
    
    if (isDenormal(Value)) printf("is denormal: ");
    printf("%e %.1f\n", Value, FLT_MAX * Value); //if using SSE it should be 0
    
    fesetenv(&Env);
    
    return 0;
}

_Bool isDenormal(float a)
{
    return ((a != 0.0f) && (fabsf(a) < FLT_MIN));
}

float GetDenormal(void)
{
    return FLT_MIN * 0.1f; //alternatively could return FLT_TRUE_MIN though for the example the result won't be as intuitive. As doing this you can see the result is FLT_MAX * FLT_MIN but pushed back one decimal place when denormals are available as you would expect.
}


Messages In This Thread
[C] Hidden features of C - by benwaffle - 12-06-2013, 10:33 PM
RE: Hidden features of C - by yrmt - 13-06-2013, 08:29 AM
RE: Hidden features of C - by bottomy - 13-06-2013, 02:32 PM
RE: Hidden features of C - by bottomy - 20-06-2013, 02:41 AM
RE: Hidden features of C - by venam - 20-06-2013, 03:01 AM
RE: Hidden features of C - by Hans Hackett - 24-06-2013, 06:57 AM
RE: Hidden features of C - by benwaffle - 08-01-2014, 09:56 PM
RE: Hidden features of C - by bottomy - 09-01-2014, 03:54 AM
RE: [C] Hidden features of C - by dami0 - 24-07-2014, 07:23 PM
RE: [C] Hidden features of C - by jmbi - 25-07-2014, 12:43 AM