4 ms·
> #define sizeof(x) (size)sizeof(x) I'm guessing this is lacking an outer pair of parentheses (i.e. it's not `((size)sizeof(x))`) on the grounds that they'r
by comex 3y ago
> #define sizeof(x) (size)sizeof(x)
I'm guessing this is lacking an outer pair of parentheses (i.e. it's not `((size)sizeof(x))`) on the grounds that they're unnecessary. In terms of operator precedence, casting binds tightly, so if you write e.g. `sizeof(x) * 3`, it expands to `(size)sizeof(x) * 3`, which is equivalent to `((size)sizeof(x)) * 3`: the cast happens before the multiplication. Indeed, casting binds more tightly than anything that could appear on the right of sizeof(x) – with one exception which is completely trivial.
But just for fun, I'll point out the exception. It's this:
(size)sizeof(x)[y]
Indexing binds more tightly than casting, so the indexing happens before the cast.
In other words, it's equivalent to `(size)(sizeof(x)[y])`, not `((size)sizeof(x))[y]`.
But you would never see that in a real program, since the size of something is not a pointer or array that can be indexed. Except that technically, C allows you to write integer[pointer], with the same meaning as pointer[integer]. Not that anyone ever writes code like that intentionally. But you could. And if you do, it will compile and do the wrong thing, thanks to the macro lacking the extra parentheses.
…On a more substantive note, I quite disagree with the claim that signed sizes are better. If you click through to the previous arena allocator post, the author says that unsigned sizes are a "source of defects" and in particular the code he presents would have a defect if you changed the signed types to unsigned. Which is true – but the code as presented also has a bug! Namely, it will corrupt memory if `count` is negative. You could argue that the code is correct as long as the arguments are valid, but it's very easy for overflow elsewhere in the code to make something accidentally go negative, so it's better for an allocator not to exacerbate the issue.
With unsigned integers, a negative count is not even representable, and a similar overflow elsewhere in the program would instead give you an extremely high positive count, which the code already checks for.
Personally I prefer to use unsigned integers but do as much as possible with bounds-checked wrappers that abort on overflow. Rarely does the performance difference actually matter.
- bjourne 3y agoThat's a good catch. The moral of the story is that unless your macro definition expands to a single token (e.g #define X 123) you should always, always, always surround it with parenthesis. Because C's precedence rules are damn complicated.
- mananaysiempre 3y ago> Because C's precedence rules are damn complicated. This particular part is not actually complicated: the postfix operators bind the most tightly, then the prefix ones, then the infix ones. (The last part is quite messy, though.) So (int)x[y] parses the same way as, for example, *p++, which should be familliar to a C programmer.
- comex 3y ago(self-reply) One more thing. > I could use _Bool, but I’d rather stick to a natural word size and stay away from its weird semantics. This is even more subjective, but personally I like _Bool's semantics. They mean that if an expression works in an `if` statement: if (flags & FLAG_ALLOCATED) then you can extract that same expression into a boolean variable: _Bool need_free = flags & FLAG_ALLOCATED; The issue is that `flags & FLAG_ALLOCATED` doesn't equal '0 if unset, 1 if set', but '0 if unset, some arbitrary nonzero value if set'. (Specifically it equals FLAG_ALLOCATED if set, which might be 1 by coincidence, but usually isn't.) This kind of punning is fine in an `if` statement, since any nonzero value will make the check pass. And it's fine as written with `_Bool`, since any nonzero integer will be converted to 1 when the expression is implicitly converted to `_Bool`. But if you replace `_Bool` with `int`, then this neither-0-nor-1 value will just stick around in the variable. Which can cause strange consequences. It means that if (need_free) will pass, but if (need_free == true) will fail. And if you have another pseudo-bool, then if (need_free == some_other_bool) might fail even if both variables are considered 'true' (i.e. nonzero), if they happen to have different values. _Bool solves this problem. Admittedly, the implicitness has downsides. If you're refactoring the code and you decide you don't really need a separate variable, you might try to replace all uses of `need_free` with its definition, not realizing that the implicit conversion to _Bool was doing useful work. So you might end up with incorrect code like: if ((flags & FLAG_ALLOCATED) == true) Also, if you are reading a struct from disk or otherwise stuffing it with arbitrary bytes, and the struct has a _Bool, then you risk undefined behavior if the corresponding byte becomes something other than 0 or 1 – because the compiler assumes that the implicit conversion to 0 or 1 has been done already.
- jstimpfle 3y ago> (size)(sizeof(x)[y]) Actually, and this is probably surprising to many, this is equivalent to (size)(sizeof ((x)[y])) sizeof is not a function but a unary operator, and indexing (as well as function calling...) binds stronger than the sizeof operator. It is not a function, not even syntactically! Hence why I strongly prefer putting a space after the sizeof keyword, and to not use parens for the operand unless needed. https://en.cppreference.com/w/c/language/operator_precedence https://en.cppreference.com/w/c/language/operator_precedence So the "correct" way to define the macro is #define sizeof(x) ((size)(sizeof (x)))
- deleted 3y ago[deleted]