Piotr Dobrogost
Piotr Dobrogost

Reputation: 42425

What's the reason behind applying two explicit type casts in a row?

What's the reason behind applying two explicit type casts as below?

if (unlikely(val != (long)(char)val)) {

Code taken from lxml.etree.c source file from lxml's source package.

Upvotes: 2

Views: 269

Answers (3)

plinth
plinth

Reputation: 49179

That's a cheap way to check to see if there's any junk in the high bits. The char cast chops of the upper 8, 24 or 56 bits (depending on sizeof(val)) and then promotes it back. If char is signed, it will sign extend as well.

A better test might be:

if (unlikely(val & ~0xff)) {

or

if (unlikely(val & ~0x7f)) {

depending on whether this test cares about bit 7.

Just for grins and completeness, I wrote the following test code:

void RegularTest(long val)
{
    if (val != ((int)(char)val)) {
        printf("Regular = not equal.");
    }
    else {
        printf("Regular = equal.");
    }
}

void MaskTest(long val)
{
    if (val & ~0xff) {
        printf("Mask = not equal.");
    }
    else {
        printf("Mask = equal.");
    }
}

And here's what the cast code turns into in debug in visual studio 2010:

movsx   eax, BYTE PTR _val$[ebp]
cmp DWORD PTR _val$[ebp], eax
je  SHORT $LN2@RegularTes

this is the mask code:

mov eax, DWORD PTR _val$[ebp]
and eax, -256               ; ffffff00H
je  SHORT $LN2@MaskTest

In release, I get this for the cast code:

movsx   ecx, al
cmp eax, ecx
je  SHORT $LN2@RegularTes

In release, I get this for the mask code:

test    DWORD PTR _val$[ebp], -256      ; ffffff00H
je  SHORT $LN2@MaskTest

So what's going on? In the cast case it's doing a byte mov with sign extension (ha! bug - the code is not the same because chars are signed) and then a compare and to be totally sneaky, the compiler/linker has also made this function use register passing for the argument. In the mask code in release, it has folded everything up into a single test instruction.

Which is faster? Beats me - and frankly unless you're running this kind of test on a VERY slow CPU or are running it several billion times, it won't matter. Not in the least.

So the answer in this case, is to write code that is clear about its intent. I would expect a C/C++ jockey to look at the mask code and understand its intent, but if you don't like that, you should opt for something like this instead:

#define BitsAbove8AreSet(x) ((x) & ~0xff)
#define BitsAbove7AreSet(x) ((x) & ~0x7f)

or:

inline bool BitsAbove8AreSet(long t) { return (t & ~0xff) != 0; } // make it a bool to be nice inline bool BitsAbove7AreSet(long t) { return (t & ~0x7f) != 0; }

And use the predicates instead of the actual code.

In general, I think "is it cheap?" is not a particularly good question to ask about this unless you're working in some very specific problem domains. For example, I work in image processing and when I have some kind of operation going from one image to another, I often have code that looks like this:

BYTE *srcPixel = PixelOffset(src, x, y, srcrowstride, srcdepth);
int srcAdvance = PixelAdvance(srcrowstride, right, srcdepth);
BYTE *dstPixel = PixelOffset(dst, x, y, dstrowstride, dstdepth);
int dstAdvance = PixelAdvance(dstrowstride, right, dstdepth);
for (y = top; y < bottom; y++) {
    for (x=left; x < right; x++) {
        ProcessOnePixel(srcPixel, srcdepth, dstPixel, dstdepth);
        srcPixel += srcdepth;
        dstPixel += dstdepth;
    }
    srcPixel += srcAdvance;
    dstPixel += dstAdvance;
}

And in this case, assume that ProcessOnePixel() is actually a chunk of inline code that will be executed billions and billions of times. In this case, I care a whole lot about not doing function calls, not doing redundant work, not rechecking values, ensuring that the computational flow will translate into something that will use registers wisely, etc. But my actual primary concern is that the code can be read by the next poor schmuck (probably me) who has to look at it.

And in our current coding world, it is FAR FAR CHEAPER for nearly every problem domain to spend a little time up front ensuring that your code is easy to read and maintain than it is to worry about performance out of the gate.

Upvotes: 10

David Sykes
David Sykes

Reputation: 49832

If val is a long then the (char) will strip off all but the bottom 8 bits. The (long) casts it back for the comparison.

Upvotes: 1

Didier Trosset
Didier Trosset

Reputation: 37437

Speculations:

  • cast to char: to mask the 8 low bits,

  • cast to long: to bring the value back to signed (if char is unsigned).

Upvotes: 1

Related Questions