Uninitalised Variables and Optimisation

What is wrong with the following code

#include <stdio.h>

int main(void)
{
        char **blah;
        char *astring = "hello, world\n";

        *blah = astring;

        printf("%s\n", *blah);

        return 0;
}

Fairly obvious when it is layed out like this; that *blah = astring should be blah = &astring (this might be less obvious when it is buried deep within several functions :). blah is uninitalised, so you can't dereference it.

Unfortunatley, this code will compile with -Wall with no warnings. This is because of a little fact

-Wuninitialized
           Warn if an automatic variable is used without first being
           initialized or if a variable may be clobbered by a "setjmp"
           call.

           These warnings are possible only in optimizing compilation,
           because they require data flow information that is computed
           only when optimizing.  If you don't specify -O, you simply
           won't get these warnings.

So always turn on at least -O to get the full checking gcc can give you, and you'll probably catch things like the above before they even segfault.

PowerPC char nuances

By default, char on PowerPC defaults to be a unsigned char, unlike most other architectures (3-8 of the ABI).

All EBCDIC machines seem to have char defined as unsigned. I wouldn't know EBCDIC if it hit me in the face, and I doubt many people born in the 80's would either. What seems more likely is that PowerPC chose this in it's ABI due to architectural limitations around type promotion of chars to integers. PowerPC doesn't have an instruction to move and sign extend all at the same time like other architectures. For example, the following code

void f(void)
{
    char a = 'a';
        int i;

        i = a;
}

produces the following ASM on 386

f:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movb    $97, -1(%ebp)
        movsbl  -1(%ebp),%eax
        movl    %eax, -8(%ebp)
        leave
        ret

See the movsbl instruction; which in AT&T syntax means "move sign extending the byte to a long". Now watch the same thing on PowerPC with --fsigned-char

f:
        stwu 1,-32(1)
        stw 31,28(1)
        mr 31,1
        li 0,97
        stb 0,8(31)
        lbz 0,8(31)
        extsb 0,0
        stw 0,12(31)
        lwz 11,0(1)
        lwz 31,-4(11)
        mr 1,11
        blr

Note here you have to do a load clearing the top bits (lbz) and then sign extend it in a separate operation (extsb). Of course, if you do that without the -fsigned-char it just loads, without the extra clear.

So, without knowing too much about the history, my guess is that the guys at IBM/Motorola/Whoever were thinking EBCDIC when they designed the PowerPC architecture where type promotion with sign extend was probably going to be a largely superfluous instruction. The world of Linux (and consequently an operating system that ran on more than one or two architectures) appeared, so they defined the ABI to have char as unsigned because that's what they had before. Now we are stuck with this little nuance.

Moral of the story: don't assume anything, be that sizeof(long) or the signed-ness of a char.

checking for NPTL

This question seems to come up constantly. A compile time check for NPTL probably isn't what you want, since you can easily turn NPTL on or off on a program with something like LD_ASSUME_KERNEL. The following snippet is, AFAIK, the best way to check for NPTL support.

#include <stdio.h>
#include <unistd.h>
#include <alloca.h>
#include <string.h>

int isnptl (void)
{
    size_t n = confstr (_CS_GNU_LIBPTHREAD_VERSION, NULL, 0);
    if (n > 0)
    {
        char *buf = alloca (n);
        confstr (_CS_GNU_LIBPTHREAD_VERSION, buf, n);
        if (strstr (buf, "NPTL"))
            return 1;
    }
    return 0;
}

int main(void)
{
    printf("NPTL: %s\n", isnptl() ? "yes" : "no");
    return 0;
}

But more important is why are you checking?.

reading rdtsc on amd64

On an 386 you can read the rdtsc by simply doing

unsigned long long result;
__asm__ __volatile__("rdtsc" : "=A" (result));

Note, however, in the gcc docs the documentation for the =A constraint has an important caveat

Specifies the a or d registers. This is primarily useful for 64-bit integer values (when in 32-bit mode) intended to be returned with the d register holding the most significant bits and the a register holding the least significant bits.

Thus this is not what you want when using amd64 in 64 bit mode with 64 bit registers. Follow the example of the kernel code, and do the shifts by hand

#define rdtscll(val) do { \
     unsigned int __a,__d; \
     asm volatile("rdtsc" : "=a" (__a), "=d" (__d)); \
     (val) = ((unsigned long)__a) | (((unsigned long)__d)<<32); \
} while(0)

footnote: why oh why can't everyone use a real 64 bit architecture?

undefined functions

So I found a bug (it's probably shouldn't really be "important" but I do include a fix). I know why this bug happens, undefined functions are assumed to return an int, so it chops off the top of a 64 bit pointer.

but why? delving into the standards is tricky. first some terms

  • a function declaration gives the function name and the paramaters. It has a return type, a name and a paramater list. Each of the paramaters is possibly named (C99 6.7.5.3.6 : ...specifies the types of, and may declare identifiers for, the parameters of the function and later, C99 6.9.1 : If the declarator includes a parameter type list, the declaration of each parameter shall include an identifier). It looks like

    int afunction(int aparamater)
    

    .

  • a function prototype is like a weak version of a function declaration. It specifies the return value and the paramaters, but doesn't give the paramater names (C99 6.2.1 : A function prototype is a declaration of a function that declares the types of its parameters). This is used for C++ compatability, I think. It looks like

    int afunction(int)
    

    So you can say a function prototype is always a function declaration, but a function declaration isn't always a prototype.

  • a function definition is where you actually write the function. It looks like

    int afunction(int aparamater) { return aparamater; }
    

    Note that a function definition is also a declaration, which is why you can happily use functions after they are defined in the source code.

Ian Lance Taylor suggested the relevant part of the C99 standard dealing with undefined functions says

6.5.2.2; Paragraph 1 : The expression that denotes the called function shall have type pointer to function returning void or returning an object type other than an array type.

The disallows undefined functions by omission; they're not mentioned so they're not allowed. Actually, a little earlier when defining exactly what an expression is (6.5.1.2) we get one definition of a primary expression

An identifier is a primary expression, provided it has been declared as designating an object (in which case it is an lvalue) or a function (in which case it is a function designator)76 76: thus, an undeclared identifier is a violation of the syntax.

An undeclared identifier is not considered a primary expression. What's an expression? Think of it as anything that can go to the right hand side of an = or as the conditional of an if( ) statement.

So, a function declaration is just another type of declarator (C99 6.5.7) which declares an identifier which is the function name (e.g. in the same way int i declares i as being an identifier of integer type, int function(void) declares function() as an identifier of a function that returns int taking no arguments).

Ian Lance Taylor goes on to say

Given traditional C usage, requiring a function declaration can be reasonably viewed as a pedantic requirement, appropriate for -pedantic-errors. In general, if you want gcc to enforce strict adherence to the relevant standard, you must use -pedantic-errors. Of course there is a very reasonable coding style in which functions should always be declared for use. For that coding style, there is -Werror-implicit-function-declaration.

The only problem with this is that no one ever actually turns on those flags, and on 32 bit system (which most of the world use) it doesn't cause an error because the pointer is the same size as an int.