Live data from Hacker News

Stop Memsetting Structures

anmolsarma.in

251–260 of 277 posts

Re: Stop Memsetting Structures

#251
Don't use C99 initializers, or not directly. Write a macro:

  struct point {
    double x, y;
  }

  #define init_point(x, y) { (x), (y) } /* C90 */

  #define init_point(x, y) { .x = (x), .y = (y) } // C99

  struct point p = init_point(0.5, 1.5);
The macro is superior because macro calls are checked for number of arguments. If you have a structure in which certain members must be initialized (to a non-default value), and such members can be added over time, that will save your butt. How? Because when you add a new member that must not be zero/null, you add a parameter to the macro. Then, the compiler will tell you all the places where the macro is called with too few parameters.

Designated initializers eliminate some of the error-prone aspects of C90 style, but don't help enforce the discipline of initializing all members, or a certain subset of members.

Functional abstraction with checked arguments beats fancy new syntax.

Re: Stop Memsetting Structures

#252
> While we’re on the topic of little annoyances, another pattern I often see is using a variable just to pass what is effectively a literal to setsockopt():

  int yes=1;
  setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (yes)) ;
> Which can instead be written as:

  setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &(int) {1}, sizeof(int));
>

I'm sorry, no it cannot. Or, rather, not without

1. Switching C90 code to C99; or

2. Switching C++ code to C99.

Good luck with 2.

GCC and Clang support this in C++ as an extension; then you're not writing in standard C++ any longer.

1 is a nonstarter in project that mandates C90.

In other words, there may be valid reasons why the code is that way other than ignorance of compound literals.

Re: Stop Memsetting Structures

#253
post #248

Earlier quoted context omitted.

> Templates are Turing complete (lazily evaluated, purely functional, and side-effect free), so whatever your language of choice can do can be backported to C++ (granted, painfully, but probably less painfully than throwing out all your legacy code and starting from scratch). This does not logically follow, since templates are evaluated at compile time and hence have restrictions on what they can do. > C++ supports z…

"since templates are evaluated at compile time and hence have restrictions on what they can do" This statement doesn't make any sense. A Lisp program can be fully converted to run at compile time; it'll just be lacking in I/O opportunities.

You do realize that I/O is two thirds of "input, processing, output": what computers do?

What app do you use that takes no input? Or produces no output? Let alone both?

Re: Stop Memsetting Structures

#254
post #175

Earlier quoted context omitted.

Or even an OffsetPtr class with overloaded arrow and dereference operator, if you swing that way. :)

What’s the runtime performance of that in-practice? Does x86, x64 and ARM have support for relative offset pointers without any performance impact? And do compilers use them?

Yes, address generation can at the very least do [reg + constant], and on x86, much much more. These are bread and butter optimizations constantly used by compilers all over the place, because this is how field access works.

Re: Stop Memsetting Structures

#255
post #231
post #229

Earlier quoted context omitted.

I don't trust any memset at all. Literally every single memset I looked at was either broken, too slow or insecure. Mostly the wellknown glibc, freebsd, msvcrt and compiler implementations.

That's quite a bold claim. Care to explain further?

I already explained it. Noone implements memory barriers in its safe variants, only compiler barriers. And the trivial variants work byte-wise, not word-wise, which is usually 8x slower.

Re: Stop Memsetting Structures

#256

Because it’s 2019! It really irritates me when people give arguments like this. I seriously don't care what year it is, I care that the code I write works. A lot of projects still use C89 because they want the widest portability. C99 isn't very new, but especially in the embedded and other niche industries, C89 (often with some extensions) is all you get. That said, "= { 0 };" is almost always usable instead of a mem…

I was once forced to change the initialization of all the structs in an old codebase that used them for almost everything.

The senior programmer that requested the change came from the application world and used the same argument.

When I told him that the compiler gave lots of warnings about being compatible with C99 through an extension, he asked me to just disable the warnings and keep on going. We did some testing and pushed the update (we had remote devices connected via GSM)

Two weeks later we were pushing a remote downgrade to the previous version because we started to detect anomalous bugs everywhere.

Re: Stop Memsetting Structures

#258

Earlier quoted context omitted.

But if you're slamming in-memory structs over the wire, you're using packed structs. You explicitly don't want padding when you use structs that way. Whether you should do this in 2019 is another question, but sometimes it's still the easiest way to implement a binary protocol.

And you're not writing code for big-endian machines to interoperate with little-endian machines. It's easy to write binary protocol code that way but it isn't portable.

It goes without saying that you have get the endian right when you store the values. That's what the endian macros are for.

Re: Stop Memsetting Structures

#259
post #248

Earlier quoted context omitted.

> Templates are Turing complete (lazily evaluated, purely functional, and side-effect free), so whatever your language of choice can do can be backported to C++ (granted, painfully, but probably less painfully than throwing out all your legacy code and starting from scratch). This does not logically follow, since templates are evaluated at compile time and hence have restrictions on what they can do. > C++ supports z…

"since templates are evaluated at compile time and hence have restrictions on what they can do" This statement doesn't make any sense. A Lisp program can be fully converted to run at compile time; it'll just be lacking in I/O opportunities.

Any reasonable compiler will time out before you can do anything too fancy at compile time.

Re: Stop Memsetting Structures

#260
post #128

Earlier quoted context omitted.

The problem is not sending structs across the network. The problem is processing any data from the network. You have to assume that data is malicious, and will attack any weakness. I’m not talking about serialisation at all - I agreed entirely that sending struct padding across the net is less than optimal :)

> You have to assume that data is malicious, and will attack any weakness. The point is that the standard cannot help you here because malicious data attacks things outside the scope of the standard.

The standard gives compilers leeway to handle “undefined” behaviour however they want.

The original purpose of “undefined behaviour” was to deal with the myriad different platform behaviours of the era (remember at that point in history 1 byte was not necessary 8 bits).

At some point the compiler writers decided to extend the effect of that to “if this behaviour is undefined in the spec” then we are allowed to treat the code as doing anything at all - so we went from this may do different things on different platforms to “the compiler can emit code that does something other than the clear intent or the platform specific behaviour”.

This change in mindset from the compiler devs has been necessary to improve performance in the spec benchmarks, and other micro-benchmarks created primarily to justify the arbitrary definition of “undefined behaviour”.

To give an idea of how capricious the compiler devs are they implemented optimizations that ended up breaking spec (it has undefined behavior), and so rolled those optimizations out. Yet when they literally add security bugs to code that has been secure for a decade, their response has been “well you’re relying on undefined behaviour”.

The whole nonsense can be summed up as a (according to the insane definition of “correct” used by the compiler writers)

Struct {char c;int i;} a,b;

Bzero(&a, size of a)

Bzero(&b, size of b)

If (something) return memcmp(&Zaki, &b, size of a);

Return 3;

Could legitimately be optimized to a function that just returns 3. Because the writes to padding in bzero aren’t uneeefined, as are the reads in memcmp. Therefore the memcmp is undefined, and the compiler can assume undefined behavior cannot occur, therefore that branch can be removed.

Whether the compiler does do that today isn’t relevant: the definition of “correct” used by compiler developers is that that is valid behaviour from them.

The reality is that modern c/c++ developers need to treat the compiler as an adversary, because of a nonsensical definition of “correct code generation” assumed by the compiler writers.

Post reply on HN