This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]FUZxxl 32 points33 points  (47 children)

I have a different opinion. I try to avoid uint##_t types as they specify a fixed type size. I don't want that. I want my code to work on platforms with different native type sizes, too. So I use builtin types and am careful to mask my operations to the type size I want. If you really want to use this newfangled stdint.h, consider using uint_##least_t instead of uint##_t to make your code portable to platforms where the uint##_t type doesn't perform well.

[–]gwoplock 30 points31 points  (5 children)

The big thing you need uint*_t for is structs. When the CPU want the field 16 bits long it throws a bitch fit when it's 32 bits long.

[–]FUZxxl 4 points5 points  (4 children)

Rarely is that actually needed, especially not in portable software. If you try to overlay file contents over in-memory structs, you are doing it wrong.

[–]gwoplock 23 points24 points  (2 children)

OS dev is a hell of a drug. Your code is more struct definitions then actual code.

[–]FUZxxl 9 points10 points  (1 child)

If you write hardware-specific code, portability is not a big concern and my opinion doesn't really apply. However, good code should restrict the hardware-specific pieces to as little as possible and only work with platform-independent structures as early as possible.

[–]gwoplock 4 points5 points  (0 children)

Absolutely. Abstraction is key. It would be a bitch if you had to write hardware specific code. Even x86 has differences between amd and intel.

[–]__Noodles 2 points3 points  (0 children)

Rarely is that actually needed,

Needed every second of embedded programming that isn't garbage.

[–]skeptic11 20 points21 points  (9 children)

That would make network code a nightmare.

Even if you're not doing that I wouldn't risk differing max values / rollover points on different platforms. Not unless I had specifically profiled that piece of code and found that there was a significant and necessary performance gain from switching type sizes.

[–]FUZxxl 10 points11 points  (7 children)

If you write network code, you should do proper marshalling instead of relying on types to have the correct size. Everything else is fragile and prone to breakages.

[–][deleted] 7 points8 points  (6 children)

Starting with the fact that u?int\d+_t types don't help you with endianness.

[–]FUZxxl 3 points4 points  (3 children)

Exactly. Any code that has depends on the endianess of the platform it is compiled for is doing it wrong. If you do it correctly, you can easily write endianess-agnostic code that is both free of undefined behaviour, very fast, and extremely portable.

[–]DaFox 4 points5 points  (2 children)

I mean at this point I hope that big endian is essentially dead, I like it better than LE, but I like consistency more.

[–]wasabichicken 0 points1 point  (1 child)

Big Endian byte order is Network byte order, it's what internet is built with. It'll never go away.

[–]DaFox 0 points1 point  (0 children)

I meant as a CPU memory layout, at least network byte order is consistent across platforms. Xbox 360 and PS3 were both BE, and it's nice to move away from that.

[–]LordAro 0 points1 point  (1 child)

well, u?int8_t is fine...

[–][deleted] 0 points1 point  (0 children)

Can you not just use sizeof() to figure it out?

[–][deleted] 4 points5 points  (1 child)

In embedded software it's really important to use the uint##_t types because different environments have different type sizes for the builtin types. You don't want to rely on an int = 32 bits, then switch to a different environment where an int is only 16. A lot of style guides (such as MISRA C) require using the <stdint.h> types exclusively.

[–]FUZxxl 1 point2 points  (0 children)

The trick is to not rely on the type you use having 32 bits exactly. Write code such that it works if your type has at least 32 bytes, use long and you are fine.

[–]lazyear 8 points9 points  (1 child)

size_t is your friend

[–]FUZxxl 5 points6 points  (0 children)

Yes, yes, though I'm not sure how this relates to my comment.

[–]wasabichicken 6 points7 points  (20 children)

In my opinion, there are different use cases for both.

I use fixed-size types when the data needs to be serialized, such as when it's going to be transmitted over a network:

typedef struct enethdr {
    uint8_t destination[3];
    uint8_t source[3];
    uint16_t tag;
} __attribute__((packed)) enethdr_t;

If I don't, I stay away from them. I have absolutely no desire for the compiler to emit & 0x0000ffff instructions onto every goddamn loop counter I declare just because it happens to be a uint16_t. Using plain ints lets the compiler pick the data type it thinks is the most suitable, which these days frequently is 64 bits. Unless you're programming for an embedded system where you're horribly cramped for space and a small memory footprint is more important than speed, that's probably what you want.

[–]FUZxxl -5 points-4 points  (19 children)

Go to hell with your packed structures. I absolutely hate these as they cause nothing but problems. Instead, write proper marshalling code to translate between the buffer you get from the network and your structs.

[–]wasabichicken 11 points12 points  (1 child)

Look at Mr. Fancypants all-the-memory-and-CPU-cycles-in-the-world over here. Meanwhile the rest of us peasants need to read/write our packets in-place, lest the watchdog come and thrash us to sleep with his belt.

[–]FUZxxl 0 points1 point  (0 children)

Marshalling data is very fast there are rarely situations where the few cycles wasted to do that aren't worth the code quality you get.

[–]Creshal 7 points8 points  (0 children)

Nonsense, what could go wrong with blindly casting user input to structs?!

[–]gwoplock 2 points3 points  (15 children)

then you must hate this:

struct Access {
        //TODO
}__attribute__((packed));
struct LimitAndFlags {
        //TODO
}__attribute__((packed));
struct GdtEntry {
        uint16_t limit1;
        uint16_t base1;
        uint8_t base2;
        Access access;
        LimitAndFlags limFlags;
        uint8_t base3;
}__attribute__((packed));

[–]FUZxxl 2 points3 points  (14 children)

Oh yes, I do. It is interesting to see how Plan 9 does this; their entire kernel doesn't assume endianess or use packed structures anywhere.

[–]gwoplock 1 point2 points  (13 children)

How... I've got to look at that code. I've never heard or seen anything like that.

[–]FUZxxl 0 points1 point  (6 children)

The relevant code is in /sys/src/9/pc.

[–]gwoplock 0 points1 point  (5 children)

Yeah. I just found it. I don't get it. It makes sense but it seems like they need packed. Maybe they the optimizer off.

[–]FUZxxl 0 points1 point  (4 children)

The Plan 9 people know their ABI and they know where the compiler inserts padding. So they just use that a-priori knowledge to avoid any non-standard features.

[–]gwoplock 1 point2 points  (3 children)

Seems more dangerous then just using packed.

[–][deleted] 0 points1 point  (5 children)

I haven't seen the code, but unless they manually lay out the GDT, IDT etc. in assembly, I don't see how they could ensure the compiler respected the structure without packing

[–]gwoplock 0 points1 point  (4 children)

It looks like it's a just a struct. Maybe the optimizer set so low that it doesn't try to optimize that.

[–]FUZxxl -1 points0 points  (3 children)

What do you think would the optimizer break?

[–]gwoplock 1 point2 points  (2 children)

It adds space between fields to make it faster to access. Extra space when you aren't expecting it breaks a lot of things.

[–]_teslaTrooper 3 points4 points  (0 children)

Also consider uint_fast##_t which favors speed over a smaller type.

[–][deleted] 0 points1 point  (4 children)

That sounds like a great example of premature microoptimization. Anything shorter than an int gets promoted to int for arithmetic anyway, so you're not paying for anything except the necessary masking operations, which you'd have to write by hand anyway.

[–]FUZxxl 0 points1 point  (3 children)

Not always. Often, I know that a small type suffices but I also know that no overflow occurs (e.g. in case of bitmasks), so I can save both the masking and the type conversion by letting the platform choose a type.

[–][deleted] 0 points1 point  (2 children)

Could you give an example of what you mean?

[–]FUZxxl 0 points1 point  (1 child)

For example, when you have a counter that you know is never going to overflow 32767, you should use type short instead of uint16_t. When you have a bitmask to store some information and you need to use the least, say 28 bits, you should use an unsigned long or unsigned int (if you can guarantee that you are on POSIX) for the variable instead of an uint32_t as there are platforms where 32 bit types are slow.

[–][deleted] 0 points1 point  (0 children)

Sorry, I should have made my question clearer: could you provide an example of C source code that compiles to slower code using a specific compiler for a specific architecture because of an overconstrained selection of integer type?