: A byte is the smallest unit you can write on (most?) x86 platforms.
Most CPUs work on byte level. If a CPU has an instruction that allows you to work on bit level, it is the task of the compiler to make sure that this particular CPU instruction is used.
In plain English, this means that an assembler programmer might have to adjust their code to this, but never a C/C++ programmer. C/C++ always work on a byte level, and if the CPU happens to have bit instructions, the compiler will translate your byte-wise instructions to bit-wise OP-codes.
: If you want to write bits, the idea is to read the bytes in which
: the bits reside and then use binary operations on the bytes to set
: the bits and then write them back to file.
: There's a catch though, and a pretty nasty one too. Bit order is
: often written to file and stored in memory in a 'mangled' way. And
: to top it off, I don't think there is óne standard that dictates how
: this is done. Actually, because of this, you as a programmer don't
: know how the actual bits are stored. There's no way to find out
: either, because you're always looking at it byte-wise (or bigger).
This is very true and the reason why you should avoid C/C++ "bit fields" like the plauge. If you use the bitwise instructions instead, you usually shouldn't need to worry about how the bits are stored in memory: the compiler will handle that for you. Some issues may rise when you port code from a little endian CPU to a big endian one, but in most cases you don't have to worry about this, as long as you use the C/C++ bitwise operators. If you use bit fields, you unleash hell.
: How will I assign a number to each bit? Which one will be
: the first, which the second... what's the 54th bit?
0x01 is always the first one if you program in C/C++. The only danger is when you are swapping the bytes of a large integer. Then you need to be aware of big/little endian.
Big endian (Motorola/Freescale, Unix, Renesas, Old macs etc):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Little endian (Intel, Pic, Atmel, New macs etc):
7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8
However, whenever you use shift << >>, bitwise & | ^ etc the compiler will handle everything for you. If you for example type (my_int & 0x0001) on a little endian machine, the compiler will still give you bit 0 as expected and not bit 8. Very convenient.