about style - why use hex instead of int in this function

Started by
16 comments, last by Unnamed User 10 years, 6 months ago

I c, so you could pass a function many flags in any order using hexadecimal form and descriptive #define calls. And what would the syntax look like to parse the haxadecimal to determine which flags where set once in the body of the function?


If you're using C++, it's considered bad practice to use #define to declare constant values. That was something that was common 20 years ago, but people have learned that it's not a good idea (through many a trial and error - and I don't mean figuratively laugh.png).

Instead, people use 'const int'/'const unsigned' or enums to create permanently unchanging values. #defines have hidden dangers to them can cause bugs if they are overused (they aren't broken - they just sometimes behave in ways people don't expect resulting in obvious-in-hindsight but hard-to-locate bugs). Older books and tutorials, and older code libraries (like Win32) use alot of #defines.

C++ code doesn't need to know the difference between hexadecimal, decimal, octal, and binary. They are all just integers of some kind or another (maybe signed, maybe unsigned, depending on the context). C++ code itself doesn't care what human format we write our numbers in. smile.png

The compiler, when compiling our code, converts our C++ language code into machine assembly.

In the exact same way, the compiler converts our human numbering systems into whatever format (binary) the computer cares about.

Try running this code:


#include <iostream>
using namespace std;

void myFunc(int someValue)
{
     std::cout << "Value: " << someValue << std::endl;
}

int main()
{
	myFunc(255);
	myFunc(0xFF);
	myFunc(0377);
	return 0;
}

There are two things to note here:

  1. The function didn't have to treat hexadecimal values any differently than 'normal' decimal values
  2. The output from the functions were exactly the same (because dec 255 == hex FF == oct 377; the values were equivalent)

The code didn't care at all. From our perspective, we wrote each number differently. From the code's perspective, it was the exact same thing.

These numbering systems can be used interchangeably for different purposes, whenever it is more convenient to write in one system over another.

Advertisement


If you're using C++, it's considered bad practice to use #define to declare constant values. That was something that was common 20 years ago, but people have learned that it's not a good idea (through many a trial and error - and I don't mean figuratively ).

Instead, people use 'const int'/'const unsigned' or enums to create permanently unchanging values. #defines have hidden dangers to them can cause bugs if they are overused (they aren't broken - they just sometimes behave in ways people don't expect resulting in obvious-in-hindsight but hard-to-locate bugs). Older books and tutorials, and older code libraries (like Win32) use alot of #defines.

Yeah, this is a good point. Instead of making macros as I had for GET_FIELD, it's probably better to declare an inline function. In ARM assembly I could rewrite that entire expression as one CPU instruction, so there's a good chance the compiler will do the same.

A question for you: Let's say you have > 1,000,000 constants of which you use only a fraction, let's say 1000. Will the compiler maintain a symbol table with all 1,000,000 entries or is it smart enough to strip away unused constants?

A question for you: Let's say you have > 1,000,000 constants of which you use only a fraction, let's say 1000. Will the compiler maintain a symbol table with all 1,000,000 entries or is it smart enough to strip away unused constants?

I don't know, optimizations at the assembly level isn't an area I've looked into! mellow.png

I've never really had anywhere near a million constants before - my projects are small hobbyist / indie developer PC games and tools, not AAA console games, so the occasional function-level and architectural-level optimizations are my projects have needed in the past.

I'm a little curious now. I may have to try an experiment and see what happens. In my day job I do embedded characterization. The SoCs we develop have tens of thousands of memory mapped registers which are all defined in a massive include file as a series of defines. If I convert them to constants and compile as a separate unit it might help with compile times. But I can't keep all those definitions in the final linked file. Ironically I don't have enough memory on the SoC itself to hold that number of constants.

Compilers tend to strip things thst aren't used unless they have some reason to expect that it would be bad to do so. You could test it by writing a tiny program that declares a few constants snd then prints one out. Turn on optimization but leave debugging enabled, the just break somewhere and look the used constant up in memory to see if the unused ones are there as well.
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

If C++ supported native binary literals, that would be even more preferable (note: binary literals will in the C++14 standard).

Or there is the old enum trick....

[source]

enum BinaryNumber
{
b00000000,
b00000001,
b00000010,
b00000011,
b00000100,
// snip
b11111100,
b11111101,
b11111110,
b11111111
};
enum Flags
{
None = b00000000,
Flag1 = b00000001,
Flag2 = b00000010,
Flag3 = b00000100,
Flag4 = b00001000,
Flag5 = b00010000,
Flag6 = b00100000,
DefaultFlags = (Flag3 | Flag2 | Flag6)
};
[/source]
personally I prefer shifts for flag values:
[source]
enum Flags
{
None = 0,
Flag1 = 1 << 0,
Flag2 = 1 << 1,
Flag3 = 1 << 2,
Flag4 = 1 << 3,
Flag5 = 1 << 4,
Flag6 = 1 << 5,
DefaultFlags = (Flag3 | Flag2 | Flag6)
};
[/source]

A question for you: Let's say you have > 1,000,000 constants of which you use only a fraction, let's say 1000. Will the compiler maintain a symbol table with all 1,000,000 entries or is it smart enough to strip away unused constants?

I don't know, optimizations at the assembly level isn't an area I've looked into! mellow.png

I've never really had anywhere near a million constants before - my projects are small hobbyist / indie developer PC games and tools, not AAA console games, so the occasional function-level and architectural-level optimizations are my projects have needed in the past.

A million constants will only be included in the final exe if you use *each* constant in an assignment or comparison. It's no different to comparing or assigning a million constant int values (an enum is an int). Just because a 32bit integer can store 4 billion+ different values, does not mean those values will automatically end up in the exe. Since both scenarios are highly unlikely, it's not really worth worrying about. Well, I suppose there is one situation. If you happen to have a switch statement will 1 million possibilities, then in theory that might happen. In practice, if the switch-cases are listed in order of smallest -> largest, then the compiler may be able to do something clever with optimisation in that case (i.e. assume an integer expression, and remove the constants entirely). Either way though, this isn't something to worry about. Constants are a good thing, and if you need 1 million of them in a switch statement, then so be it (just don't ask me to maintain the code!)

(The above advice assumes C++ is being used. If it's a .NET language, and you're compiling a class library, then things may be different. It's still not worth worrying about though. Chances are 1 million integer constants are still more efficient than 1 million string comparisons!)

Compilers tend to strip things thst aren't used unless they have some reason to expect that it would be bad to do so. You could test it by writing a tiny program that declares a few constants snd then prints one out. Turn on optimization but leave debugging enabled, the just break somewhere and look the used constant up in memory to see if the unused ones are there as well.

There's no need to do this. The constants won't be there.

I just think it's way easier to write and read this
enum MyFlags
{
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4,
    Flag4 = 0x8,
    Flag5 = 0x10,
    Flag6 = 0x20,
    Flag7 = 0x40,
    Flag8 = 0x80
}
Than this
enum MyFlags
{
    Flag1 = 1,
    Flag2 = 2,
    Flag3 = 4,
    Flag4 = 8,
    Flag5 = 16,
    Flag6 = 32,
    Flag7 = 64,
    Flag8 = 128
}
Both ways are easy to do, if I use decimal I just have to double the last value, but with hexadecimal I just have to remember the sequence 1, 2, 4 and 8 as it will just repeat itself over and over, just adding zeros to the end

This topic is closed to new replies.

Advertisement