Abramo Bagnara

Il 19/12/21 02:43, Nicolas Pitre ha scritto:
On Fri, 17 Dec 2021, Abramo Bagnara wrote:

In the specific of your PR: have you considered that if, instead to keep
increasing buf, you increase a buffer offset you would avoid to do ugly tricks
and NULL subtraction?
Well... I took this as a challenge and attempted it.
And it didn't work satisfactorily.
The problem is that the buf pointer accomplishes two roles: it serves as
a buffer offset as well as a memory boundary indicator used for argument
alignment. Going with a buffer base and an explicit offset doesn't allow
for proper alignment without also taking the buffer base into account.
If the buffer base is NULL then you need yet another variable to hold
the misalignment. This complexifies the code, increases register
pressure, makes resulting binary bigger, and doesn't look any better in
the end.
So my efforts resulted in the following PR instead which I hope would be
an acceptable middle ground.
As long as there is still an undefined behavior in the code, I'd like you resign yourself to the fact that this cannot be accepted.

Let me try another time to explain, I hope to not make you bored and perhaps you can see that under a different perspective, hopefully useful.

The easy resume is that you are stating that pointer arithmetic with NULL pointer, despite being declared undefined behavior in all C standards and despite not being a documented GCC extension is safe.

You give proofs of that showing generated assemblers of pointer differences and showing this has been translated as you expect.

You seem to have forgot that in some obsolete compilers the offsetof macro was implemented in this way and that this has been changed ages ago due to such undefined behavior (GCC has __builtin_offsetof exactly for this reason). This is a sensible proof that also GCC authors does not rely on the fact that code generation would have worked correctly to implement such macro using null pointer arithmetic.

But suppose you still don't trust me and then try to reason on similar cases. Let consider signed addition, as an another example of possible source of undefined behavior.

If you write:

int add(int x, int y) { return x + y; }

and check the generated code in almost any contemporary target you will see that it does wrapping.

Following the same reasoning you do above we might deduce that it is harmless and I can count on wrapping signed arithmetic.

But let try something different:

int is_max_int(int x) {
return x + 1 < x;

If signed arithmetic does wrapping on this compiler (as I've "proved" with the test above) then this implementation shall do what I expect, i.e. return truish for the maximum representable int.

But if you take a look to generated code you will see that it always return 0.

So we are forced to deduce that either the compiler is buggy or the proof is wrong.

I can confirm that the compiler do what the language standard require and that for both undefined behaviors the proof is wrong and despite how hard you try you *cannot* write a test (or a thousand tests) to prove that an undefined behavior behave *always* as you expect (unless it is a documented compiler extension and then it is no longer an undefined behavior).

I hope this is useful for you or others in this list.

Abramo Bagnara


Join to automatically receive all group messages.