1. 37
  1.  

  2. 8

    This should really have the pdf label.

    1. 20

      Hit the suggest link under the title when you see tag/title issues. Suggestions are visible to mods, and if a few users make the same suggestions the story’s automatically updated.

      1. 8

        Awesome. Didn’t know about that.

    2. 6

      Yes, strict aliasing was a mistake. While Rust memory model is in development, it is generally agreed that Rust will have no strict aliasing.

      1. 1

        I didn’t think Rust had any way of creating aliasing pointers/references that weren’t of the same type (except perhaps via “unsafe” code) which makes the “has strict aliasing / has no strict aliasing” decision somewhat redundant. Do you mean that pointers generated via unsafe code are always assumed to be able to alias any other pointer, even of another type?

        1. 3

          Yes. Unsafe Rust is part of Rust. More contentious is whether pointers are integers. Tentatively they are not, just as in C.

          1. 1

            I’m not questioning whether unsafe Rust is part of Rust. I’m questioning whether aliasing pointers with different types and dereferencing them will always give defined behaviour. (And maybe it will; but it seems an odd decision to make, seeing as Rust is generally ok with unsafe code producing being able to produce undefined behaviour, and this doesn’t seem like a worthwhile behaviour to define).

            1. 1

              Everyone that I know of is against TBAA. I don’t know enough about the details to tell you why, but /u/sanxiyn is correct.

              And maybe it will; but it seems an odd decision to make, seeing as Rust is generally ok with unsafe code producing being able to produce undefined behaviour,

              This is true and not true, that is, UB has a cost, in that it’s a footgun. If you don’t get much optimization benefit, then you’re introducing a footgun for no benefit. One of our goals for the unsafe code guidelines is that they will not require large swaths of already existing unsafe code to be unwritten.

              1. 1

                Everyone that I know of is against TBAA.

                You can consider that untrue as of now. :)

                I don’t know enough about the details to tell you why, but /u/sanxiyn is correct

                Ok, thanks. Interesting to know.

                1. 1

                  I meant, the people inside of Rust. Otherwise that’d be a hell of a statement :)

      2. 4

        This whole area has been exercising my brain recently.

        As much as I hate the C standard committee’s lack of courage in defining behaviour, as often a simple decision, even if controversial will resolve it.

        However, here is one that is sort of unresolvable.

        What behaviour should a program have that indexes beyond the bounds of an array?

        There is no way a standard can prescribe what the result will be.

        It must be undefined.

        So the obvious thing to do, as Pascal did, is do bounds checking and have a defined behaviour.

        That imposes substantial runtime costs in CPU and memory, so users do switch it off…..

        So what should the behaviour be?

        One reasonable assumption a compiler writer can make is that there is no way the programmer can intend to index out of bounds, so I can assume that the index is less than the bound and generate machine code accordingly.

        You might say, these newfangled optimizations are the problem… no they aren’t.

        Compilers have been relaying out data in memory according what they think best for decades.

        Where this whole thing is driving me nuts is around asserts. See this thread I started here… https://readlist.com/lists/gcc.gnu.org/gcc-help/7/39051.html

        Asserts, if they are compiled in, tell the compiler (without getting involved in UB optimizations) that if expression is false, then everything down stream is not reachable…. so it analyzes under the assumption the expression is true.

        However it doesn’t attempt to warn you if it finds a code path where the expression is false, and completely redoes it’s optimization without that assumption if you compile the assert out.

        1. 4

          What behaviour should a program have that indexes beyond the bounds of an array?

          There is no way a standard can prescribe what the result will be.

          It must be undefined.

          This gets to the heart of the matter, I think. Part of the issue is people confuse “the language standard” with “what compilers do”. The language says it is undefined behaviour for an out-of-bounds array access to occur, or for signed integers to have their value range exceeded, but there’s no reason why compilers can’t generate code which will explicitly detect these situations and throw out an error message (and terminate).

          So why don’t compilers generally do that by default? Because C is used in performance critical code where these checks have a cost which is considered significant. And, despite the claims in this article, there are cases where trivial optimisations such as assuming that signed integer arithmetic operations won’t overflow can lead to significant speedups (it’s just that these cases are not something as trivial as a single isolated loop).

          If you do value deterministic behaviour on program error and are willing to sacrifice some performance to get it, the obvious solution is to use a language which provides that, i.e. don’t use C. But that’s not a justification to criticise the whole concept of undefined behaviour in C.

          1. 4

            There is a false choice between inefficient code with run time bounds checking and compiler “optimizations” that break working code. I love the example in http://www.complang.tuwien.ac.at/kps2015/proceedings/KPS_2015_submission_29.pdf where the GCC developers introduce a really stupid UB based “optimization” that broke working code and then found, to their horror, that it broke a benchmark. So they disabled it for the benchmark.

            And, despite the claims in this article, there are cases where trivial optimisations such as assuming that signed integer arithmetic operations won’t overflow can lead to significant speedups (it’s just that these cases are not something as trivial as a single isolated loop).

            Great. Let’s see an example.

            But that’s not a justification to criticise the whole concept of undefined behaviour in C.

            I think this attitude comes from a fundamental antipathy to the design of C or a basic misunderstanding of how it is used. C is not Java or Swift - and not because its designers were stupid or mired in archaic technology.

            1. 4

              There is a false choice between inefficient code with run time bounds checking and compiler “optimizations” that break working code

              Optimisations don’t break working code. They cause broken code to have different observable behaviour.

              And, despite the claims in this article, there are cases where trivial optimisations such as assuming that signed integer arithmetic operations won’t overflow can lead to significant speedups (it’s just that these cases are not something as trivial as a single isolated loop).

              Great. Let’s see an example.

              I don’t have a code example to hand, and as I said they’re not trivial, but that doesn’t mean it’s not true. Since it can eliminate whole code paths, it can affect the efficacy of for example value range propagation, affect inlining decisions, and have other flow-on effects.

              I think this attitude comes from a fundamental antipathy to the design of C or a basic misunderstanding of how it is used

              I disagree.

              1. -1

                Optimisations don’t break working code. They cause broken code to have different observable behaviour.

                That’s a legalistic answer. The code worked as expected and produced the correct result. The “optimization” caused it to misfunction.

                I don’t have a code example to hand

                Apparently nobody does. So the claimed benefit is just hand waving.

                I disagree.

                The thinking of Lattner is indicative. He agrees that compiler behavior using the UB loophole makes C a minefield. His solution is to advocate Swift. People who are hostile to the use of C should not be making these decisions.

                1. 5

                  That’s a legalistic answer.

                  Alas, in absence of “legalistic answers”, the only definition of C is either…

                  • An implementation of C is a valid implementation iff every program in a Blessed Set of programs compile and runs successfully and outputs exactly the same values.

                    or

                  • An implementation of C is a valid implementation iff, every program that compiles and runs successfully on The One True Blessed C Compiler, compiles and runs and outputs exactly the same values AND every program that fails to compile on The One True Blessed Compiler, fails to compile on the candidate compiler.

                  What sort of C are you envisioning?

                  Those may be appropriate ways to progress, but that is a different language and probably should be called something other than C.

                  1. 3

                    Apparently nobody does. So the claimed benefit is just hand waving

                    Again, I don’t agree.

                    1. 1

                      You can disagree all you want, but you also seem to be unable to produce any evidence.

                      1. 3

                        You can disagree all you want, but you also seem to be unable to produce any evidence.

                        I have high confidence that I could produce, given some time, an example of code which compiled to say 20 instructions if integer overflow were defined and just 1 or 2 otherwise, and probably more by abusing the same technique repeatedly, but you might then claim it wasn’t representative of “real code”. And then if I really wanted to satisfy you I would have to find some way to trawl through repositories to identify some piece of code that exhibited similar properties. It’s more work than I care to undertake to prove my point here, and so I suppose you have a right to remain skeptical.

                        On the other hand, I have at least explained (even if only very briefly) how small optimisations such as assuming that integer arithmetic operations won’t overflow could lead to significant differences in code generation, beyond simple exchanging of instructions. You’ve given no argument as to why this couldn’t be the case. So, I don’t think there’s any clearly stronger argument on either side.

                        1. 0

                          I have high confidence that I could produce, given some time, an example of code which compiled to say 20 instructions if integer overflow were defined and just 1 or 2 otherwise

                          I have no confidence of this, and it would be a completely uninteresting optimization in any case.

                          On the other hand, I have at least explained (even if only very briefly) how small optimisations such as assuming that integer arithmetic operations won’t overflow could lead to significant differences in code generation, beyond simple exchanging of instructions.

                          Not really. You are omitting a single instruction that almost certainly costs no cycles at all in a modern pipelined processor. Balance that against putting minefields into the code - and note there is no way in C to check for this condition. The tradeoff is super unappealing.

                          1. 2

                            Not really. You are omitting a single instruction

                            No, I was not talking about omitting a single instruction.

            2. 2

              With assert(), you are telling the compiler that at this point, this is true. The compiler is trusting your assertion of the truth at that point.

              Also, if you compile with -DNDEBUG -O3 you will get the warning:

              [spc]saltmine:/tmp>gcc -std=c99 -Wall -Wextra -pedantic -DNDEBUG -O3 c.c
              c.c: In function ‘main’:
              c.c:7:20: warning: ‘*((void *)&a+10)’ is used uninitialized in this function [-Wuninitialized]
              c.c:13:8: note: ‘a’ was declared here
              [spc]saltmine:/tmp>gcc -std=c99 -Wall -Wextra -pedantic -O3 c.c
              [spc]saltmine:/tmp>
              
              1. 2

                No, that is a meaningless statement.

                The compiler doesn’t even see an assert statement, let alone “trust it”.

                It is a macro that gets expanded to “plain old code” at preprocessor time, so depending on NDEBUG settings it expands either to something like if(!(exp))abort() or nothing.

                What the compiler does trust is the __attribute_((__noreturn__)) on the abort() function.

                1. 1

                  My file:

                  #include <assert.h>
                  
                  int foo(int x)
                  {
                    assert(x >= 0);
                    return x + 5;
                  }
                  

                  My file after running it through the C preprocessor:

                  # 1 "x.c"
                  # 1 "/usr/include/assert.h"
                   
                   
                  
                   
                   
                   
                  
                   
                  # 12
                  
                  # 15
                  
                  #ident      "@(#)assert.h   1.10    04/05/18 SMI"
                  
                  # 21
                  
                  # 26
                  extern void __assert(const char *, const char *, int);
                  # 31
                  
                  # 35
                  
                  # 37
                  
                   
                  # 44
                  
                  # 46
                  
                  # 52
                  
                  # 63
                  
                  # 2 "x.c"
                  
                  int foo(int x)
                  {
                     ( void ) ( ( x >= 0 ) || ( __assert ( "x >= 0" , "x.c" , 5 ) , 0 ) );
                    return x + 5;
                  }
                  #ident "acomp: Sun C 5.12 SunOS_sparc 2011/11/16"
                  

                  Not an __atttribute__ to be found. This C compiler can now generate code as if x is never a negative value.

                  1. 1

                    #include <assert.h> int foo(int x) { assert(x >= 0); return x + 5; }

                    Can you copy paste the assembly output? (I read sparc asm as part of my day job….)

                    I’d be interested to see is it is treating __assert() as anything other than a common or garden function.

                    1. 1

                      I’m not sure what it’ll prove, but okay:

                          cmp     %i0,0
                          bge     .L18
                          nop
                      
                      .L19:
                          sethi   %hi(.L20),%o0
                          or      %o0,%lo(.L20),%o0
                          add     %o0,8,%o1
                          call    __assert
                          mov     6,%o2
                          ba      .L17
                          nop
                      
                          ! block 3
                      .L18:
                          ba      .L22
                          mov     1,%i5
                      
                          ! block 4
                      .L17:
                          mov     %g0,%i5
                      
                          ! block 5
                      .L22:
                      
                      !    7    return x + 5;
                      
                          add     %i0,5,%l0
                          st      %l0,[%fp-4]
                          mov     %l0,%i0
                          jmp     %i7+8
                          restore
                      
                          ! block 6
                      .L12:
                          mov     %l0,%i0
                          jmp     %i7+8
                          restore
                      

                      I did not specify any optimizations, and from what I can tell, it calls a function called __assert().

                      1. 1

                        TL;DR; The optimiser for this compiler is crap. And it isn’t treating __assert() as special / noreturn.

                        int foo(int x)
                        {
                           ( void ) ( ( x >= 0 ) || ( __assert ( "x >= 0" , "x.c" , 5 ) , 0 ) );
                          return x + 5;
                        }
                        
                            ;; x is register %i0
                            cmp     %i0,0               ; Compare x with 0
                            bge     .L18                ; If it is large branch to .L18
                            nop                         ; Delay slot. Sigh sparc pipelining is makes debugging hard.
                        
                        ;;; This is the "call assert" branch. gcc has function __attribute__((cold)) or
                        ;;; __builtin_expect() to mark this as the unlikely path.
                        .L19:
                            sethi   %hi(.L20),%o0       
                            or      %o0,%lo(.L20),%o0
                            add     %o0,8,%o1
                            call    __assert
                            mov     6,%o2               ;Delay slot again
                            ba      .L17                ; Branch absolute to .L17
                            nop                         ;Delay slot
                        
                            ;; Really? Is this optimized at all?
                            ! block 3
                        .L18:
                            ba      .L22                ; Branch absolute to .L22!
                            mov     1,%i5               ; put 1 in %i5
                        
                        ;;; Seriously? Is this thing trying to do it the hard way?
                        ;;; The assert branch sets %i5 to zero.
                            ! block 4
                            .L17:
                            ;; Fun fact. %g0 is the sparc "bit bucket" reads as zero, ignores anything written to it.
                            mov     %g0,%i5             
                        
                            ! block 5
                            ;; Falls through. ie. Expected to come 
                            ;; out of __assert() *hasn't treated __assert as noreturn!*
                        
                            ;; Joins with the x>=0 branch
                        .L22:
                        
                        !    7    return x + 5;
                            ;; Local register %l0 is x + 5
                            add     %i0,5,%l0
                            st      %l0,[%fp-4]         ;WTF? Has this been inlined into a larger block of code?
                            mov     %l0,%i0             ;WTF? as above?
                            jmp     %i7+8               ;Return to calling addres.
                            restore                     ;Unwind sparc register windowing.
                        
                            ;; WTF? No reference to label .L12
                            ! block 6
                        .L12:
                            mov     %l0,%i0
                            jmp     %i7+8
                            restore
                        
                2. 1

                  Actually, that isn’t quite what happens….

                  Actually, it’s “Just Another Macro” which, very approximately, expands to …

                   if( !(exp)) abort();
                  

                  …where abort() is marked __attribute__((noreturn));

                  Which is almost, but not quite what one would want….

                  As the compiler uses the noreturn attribute to infer that if !exp, then rest of code is unreachable, therefore for rest of code exp is true.

                  Alas, I have found that it doesn’t, if it finds a path for which exp is false, warn you that you will abort!

                  I certainly feel there is room for compiler and optimizer writers work with design by contract style programmers to have a “mutually beneficial” two way conversation with the programmers when they write asserts.

                  1. 0

                    Which is almost, but not quite what one would want….

                    I’m not sure I understand you. assert() will abort if the expression given is false. That’s what it does. It also prints where the expression was (it’s part of the standard). If you don’t want to abort, don’t call assert(). If you expect that assert() is a compile-time check, well, it’s not.

                    I certainly feel there is room for compiler and optimizer writers work with design by contract style programmers to have a “mutually beneficial” two way conversation with the programmers when they write asserts.

                    There’s only so far that can go though. Put your foo() function in another file, and no C compiler can warn you.

                    assert() is also a standard C function, which means the compiler can have built-in knowledge of its semantics (much like a C compiler can replace a call to memmove() with inline assembly). The fact that GCC uses its __attribute__ extension for this doesn’t apply to all other compilers.

                    1. 2

                      That’s the other bit of Joy about C.

                      There are two entirely separate things….

                      The compiler.

                      And the standard library.

                      gcc works quite happily with several entirely different libc’s.

                      assert.h is part of libc, not the compiler.

                      How assert() is implemented is the domain of the libc implementer not the compiler writer.

                      I have poked at quite a few different compilers and even more libc’s…. as I have summarised is how all I have looked at are doing things. (Although some don’t have a concept of “noreturn” so can’t optimize based on that)

                      Which compiler / libc are you looking at?

                      1. 3

                        The compiler that comes with Solaris right now.

                        You can’t have a standard C compiler without the standard C library. I can get a compiler that understands, say, C99 syntax, but unless it comes with the standard C library, it can’t be called a compliant C99 compiler. The standard covers both the language and the library. I’m reading the C99 standard right now, and here’s an interesting bit:

                        Each library function is declared, with a type that includes a prototype, in a header, (182) whose contents are made available by the #include preprocessing directive.

                        And footnote 182 states:

                        (182) A header is not necessarily a source file, nor are the < and > delimited sequences in header names necessarily valid source file names.

                        To me, that says the compiler can have knowledge of the standard functions. Furthermore:

                        Any function declared in a header may be additionally implemented as a function-like macro defined in the header … Likewise, those function-like macros described in the following subclauses may be invoked in an expression anywhere a function with a compatible return type could be called.(187)

                        (187) Because external identifiers and some macro names beginning with an underscore are reserved, implementations may provide special semantics for such names. For example, the identifier _BUILTIN_abs could be used to indicate generation of in-line code for the abs function. Thus, the appropriate header could specify #define abs(x) _BUILTIN_abs(x) for a compiler whose code generator will accept it. In this manner, a user desiring to guarantee that a given library function such as abs will be a genuine function may write #undef abs whether the implementation’s header provides a macro implementation of abs or a built-in implementation. The prototype for the function, which precedes and is hidden by any macro definition, is thereby revealed also.

                        So the compiler can absolutely understand the semantics of standard C calls and treat them specially. Whether a C compiler does so is implementation defined. And good luck writing offsetof() of setjmp()/longjmp() portably (spoiler: you can’t—they’re tied to both the compiler and architecture).

                        So, getting back to assert() and your issues with it. Like I said, the compilers knows (whether it’s via GCC’s __attribute__(__noreturn__) or because the compiler has built-in knowledge of the semantics of assert()) that the expression used must be true and can thus optimize based on that information, much like it can remove the if statement and related code:

                        const int debug = 0;
                        
                        {
                          int x = debug;
                        
                          if (x)
                          {
                            fprintf(stderr,"here we are!\n");
                            exit(33);
                          }
                          // ...
                        }
                        

                        even through x, because debug is constant, x is loaded with a constant, and not modified prior to the if statement. Your wanting a warning about an invalid index to an array whose index is used in assert() is laudable, but to the compiler, you are telling it “yes, this is fine. No complaints please.” Compile the same code with NDEBUG defined, the assert() goes away (from the point of view of the compiler phase) and the diagnostic can be issued.

                        Yes, it sucks. But that’s the rational.

                        The intent is you run the code, you get the assert, you fix the code (otherwise, why use assert() in the first place?) or remove the assert() because the assumption made is no longer valid (this has happened to me but not often and usually after code has changed, which is something you want, no?).

                3. 2

                  You can do #define assert(p) if (!(p)) __builtin_unreachable() to keep the optimisation benefits! And MSVC has __assume which behaves similarly.

                  1. 1

                    Hmm… Interesting….

                    Does it then elide the expression !(p)?

                    Or does it impose the run time cost of evaluating !(p) and not the benefit of invoking abort()?

                    1. 1

                      Since __builtin_unreachable only exists to guide the compiler, p has to be an expression with no side effects, and then the compiler can optimise it out because you don’t use its result.

                  2. 1

                    I think this is an incorrectly framed question. C says that it’s not the compiler’s problem. You index past an array bound, perhaps you know what you are doing or perhaps not. The compiler is just supposed to do what you said. If you have indexed into another data structure by mistake or past the bound of allocated memory - that’s on the programmer ( BTW: I think opt-in bounds checked arrays would be great). It is unreasonable for the compiler to assume things that may be false. For example, if the programmer cautiously adds a check for overflow, I don’t want the compiler to assume that the index must be in bounds so the check can be discarded.

                    1. 6

                      C says that it’s not the compiler’s problem

                      Actually the C standard says it’s not the compilers problem, it’s undefined behaviour and completely your problem.

                      If you want it to have some weird arsed, but well defined behaviour, you need a different language standard.

                      In C standardese, things that are “the compilers problem” are labelled clearly as “implementation defined”, things that are your problem are labelled “undefined behaviour”.

                      perhaps you know what you are doing or perhaps not.

                      Well, actually, you provably don’t know what you’re doing…. as the compiler and linker lays out the data structures in ram pretty much as they damn well feel like.

                      Part of that for, example, like the struct padding and alignment is part of the ABI for that particular system, which is not part of the C standard, and most of that will change as you add or remove other data items and/or change their types. If you need to rely on such things, there are other (some non-standard) mechanisms, eg. unions types and packing pragmas.

                      BTW: I think opt-in bounds checked arrays would be great

                      gcc and clang now does have sanitizers to check that.

                      However the C standard is sufficiently wishy-washy on a number of fronts, there are several corner cases that are uncheckable, and valgrind is then your best hope. Valgrind won’t help you, for example, if you index into another valid memory region or alignment padding.

                      For example, if the programmer cautiously adds a check for overflow, I don’t want the compiler to assume that the index must be in bounds so the check can be discarded.

                      How ever, if the compiler can prove that the check always succeeds, then the check is useless and the programmer has written useless code and the compiler rightly elides it.

                      Modern versions of gcc will (if you have the warnings dialled up high enough, and annotated function attributes correctly) will warn you about tautologies and unreachable code.

                      1. 1

                        The C standard is not the C language. It is a committee report attempting to codify the language. It is not made up of laws of physics - it can be wrong and can change. My argument is that the standard is wrong. Feel free to disagree, but please don’t treat the current instance of the standard as if they were beyond discussion.

                        In fact, I do want a different standard: one that is closer to my idea what the rules of the language should be in order to make the language useful, beautiful, and closer to the spirit of the design.

                        The compiler and linker don’t have total freedom to change layouts even in the current standard - otherwise, for example, memcpy would not work. Note: “Except for bit-fields, objects are composed of contiguous sequences of one or more bytes, the number, order, and encoding of which are either explicitly specified or implementation-defined.”

                        struct a{ int x[100];}; char *b = malloc(sizeof(int)*101; struct a *y = (struct a *)b; if(sizeof(struct a)) != sizeof(int)*100 ) panic(“this implementation of C won’t work for us\n”); …. do stuff … y->x[100] = checksum(y);

                        But worse,

                        message = readsocket(); for(i = 0; i < message->numberbytes; i++) if( i > MAX)use(m->payload[i]))

                        if the compiler can assume the index is never greater than the array size and MAX is greater than array size, according to you it should be able to “optimize” away the check.

                        How ever, if the compiler can prove that the check always succeeds, then the check is useless and the programmer has written useless code and the compiler rightly elides it.

                        This is one of the key problems with UB. The compiler can assume there is no UB. Therefore the check is assumed unnecessary. Compilers don’t do this right now, but that’s the interpretation that is claimed to be correct. In fact, in many cases the compiler assumes that the code will not behave the way that the generated code does behave. This is nutty.

                        1. 8

                          The C standard is not the C language.

                          Hmm. So what is The C Language?

                          In the absence of the standard, there is no “C Language”, merely a collection of competing implementations of different languages, confusingly all named “C”.

                          I don’t think calling a standard “Wrong” isn’t very helpful, as that would imply there exists some definition of Right.

                          I rather call it “differing from all known implementations” or “unimplementable” or “undesirable” or “just plain bloody silly”.

                          There is no One True Pure Abstract Universal C out there like an ancient Greek concept of Numbers.

                          There are only the standard(s) and the implementations.

                          In fact, I do want a different standard: one that is closer to my idea what the rules of the language should be in order to make the language useful, beautiful, and closer to the spirit of the design.

                          Ah, the Joys of Standards! They are so useful, everybody wants their own one! ;-)

                          Except for bit-fields, objects are composed of contiguous sequences of one or more bytes, the number, order, and encoding of which are either explicitly specified or implementation-defined.”

                          Umm. So, yes, an array is a contiguous sequence, but we’re talking about indexing out of bounds of an array. So what is contiguous beyond that array?

                          Answer 1 : Possibly empty padding to align the next object at the appropriate alignment boundary.

                          Answer 2: Which is the next object? That is determined by the field order within a struct…. (with the alignment padding determined by the ABI), but if the array is not in a struct…. it’s all bets off as to which object the compiler linker chooses to place next.

                          Hmm. Your example didn’t format nicely (nor was it valid syntax (missing parenthesis) so let me see if I can unravel that to see what you mean…

                          struct a { 
                             int x[100];
                          }; 
                          char *b = malloc(sizeof(int)*101); 
                          struct a *y = (struct a *)b; 
                          
                          if(sizeof(struct a)) != sizeof(int)*100 ) 
                              panic(“this implementation of C won’t work for us\n”); 
                          
                          …. do stuff … 
                          
                           y->x[100] = checksum(y);
                          

                          Hmm. Not sure what you’re trying to say, but try this one….

                          #include <stdio.h>
                          #include <stdint.h>
                          
                          struct {
                              char c[5];
                              uint32_t i;
                          } s;
                          
                          uint64_t l;
                          
                          int main(void)
                          {
                             printf( "sizeof(s)=%lu\n sizeof(c)=%lu\n sizeof(i)=%lu\n", sizeof(s),sizeof(s.c),sizeof(s.i));
                             printf( "address of s=%08lx\n address of l=%08lx\n diff = %ld\n", (uintptr_t)&s, (uintptr_t)&l, ((intptr_t)&s-(intptr_t)&l));
                             return 0;
                          }
                          

                          Outputs…

                          sizeof(s)=12                                                                                                                                                                                 
                          sizeof(c)=5                                                                                                                                                                                 
                          sizeof(i)=4                                                                                                                                                                                 
                          address of s=00601050                                                                                                                                                                        
                          address of l=00601048                                                                                                                                                                       
                          diff = 8                                                                                                                                                                                    
                          

                          https://onlinegdb.com/B1Ut3031m

                          1. 3

                            In the absence of the standard, there is no “C Language”, merely a collection of competing implementations of different languages, confusingly all named “C”.

                            What a weird idea. So much of the core code of the internet and infrastructure was written in an impossible language prior to the first ANSI standard. And it even worked!

                            Ah, the Joys of Standards! They are so useful, everybody wants their own one! ;-)

                            There is a standards process. It involves people presenting proposals for modifications and discussing their merits. There are changes ! That’s totally normal and expected.

                            The moment C compilers began padding, every compiler added a “packed” attribute. The reason is that many C applications require that capability. Imagine ethernet packets with artisanal compiler innovative ordering. And those attributes are not in the standard - yet they exist all the same.

                            Your example is not my example.

                            1. 2

                              So much of the core code of the internet and infrastructure was written in an impossible language prior to the first ANSI standard. And it even worked!

                              Were actually written in whatever dialect was available on the day and worked for that machine, that compiler on that day.

                              And porting to a different machine, different compiler, different version of the compiler, was a huge pain in the ass.

                              I know.

                              I’ve done a hell of a lot of that over the decades.

                              Role on tighter standards please.

                              Yup, and most of them added a subtly different packed attribute, and since it was not terribly well documented and defined, I’ve had a fair amount of pain from libraries written (LWIP comes to mind), where at various points in their history got the packed attribute wrong, so it wasn’t portable from 32 to 64 bit.

                              Your example is not my example.

                              You example wasn’t formatted, and I didn’t quite understand the point of it. Can you format it properly and expand a bit on what you were sayign with that example?

                  3. 3

                    Does anyone know why this needed to be in a PDF image? Looks like it would make a great article!

                    1. 18

                      I find it easier to write in latex. Don’t judge me.

                      1. 4

                        Have you tried pandoc to convert LaTeX source into HTML (or any other format you want)? Would seem to be a compromise, unless you use some esoteric LaTeX commands.

                        But at the same time I can’t really understand how a typesetting macro set would be easier to use than something like markdown…

                        1. 3

                          I can’t really understand how a typesetting macro set would be easier to use than something like markdown…

                          Why not both? (I wish it were easier to run this locally.)

                          1. 3

                            That in turn reminds me of Org-mode, since it too can embed LaTeX codes, source code and has multiple export facilities. But you have to be an Emacs user to get it’s full potential.

                            1. 2

                              Oh, I didn’t know that; I’ve been meaning to try it, since I’m evaluating Spacemacs/evil-mode anyway. Thanks!

                            2. 1

                              Interesting program and Koka language is really interesting. Thanks

                            3. 1

                              my experiments with pandoc did not produce good results.

                              My preference for xelatex is not defensible. It’s just a habit.

                            4. 2

                              I don’t know enough about latex to judge it, but if your output was a document instead of an image, it would be a lot easier to read on reader devices (like tablets)!

                          2. 2

                            I have several tabs open on Wittgenstein (check this out) and I got so confused thinking I was in the wrong one haha

                            1. 1

                              Very interesting. This whole idea seems to be in the zeitgeist. Here’s a thread about Munchausen’s Trilemma: https://news.ycombinator.com/item?id=17043954. And I commented in it about Hofstadter, and Lewis Carroll’s “What the Tortoise said to Achilles.”

                              To my layman’s eye all these sources spanning a century seem to be saying the same thing. Without reference to reach other, for the most part?

                            2. 1

                              What I don’t get about UB in these discussions: isn’t it possible to get the tooling to bail on optimizations that cross a UB barrier?

                              Like OK you might dereference a null pointer here, this is UB, but don’t take that as a “well let’s just infer that anything’s doable here”. Instead take a “the result is unknowable, so don’t bother optimizing”.

                              Is there some sort of reason UB-ness can’t be taken into account in these optimization paths? Is there some… fundamental thing about C optimization that requires this tradeoff?

                              1. 2

                                I think the problem with this idea is that many optimisations rely on something being undefined behaviour.

                                In any case, the result is not just “unknowable” - there is no result. If I have two signed integer values for which the sum exceeds the largest possible signed integer value, and I add them together, there is not some unknown value that is the result. There is in fact a known value which is the result but which cannot be represented by the type which the result of the operation needs to be expressed in. What does it mean to “not bother optimising” here? Yes, the compiler could decide that the result of such an operation should be an indeterminate value, rather than undefined behaviour, but that will inhibit certain optimisations that could be possible even in cases where the compiler cannot be sure that the indeterminate value will actually ever result.

                                1. 1

                                  People keep saying this, but I still have not seen a well worked out example of how UB produces real optimizations let alone a respectable study.

                                  But do “optimizations” actually produce speedups for benchmarks? Despite frequent claims by compiler maintainers that they do, they rarely present numbers to support these claims. E.g., Chris Lattner (from Clang) wrote a three-part blog posting8 about undefined behaviour, with most of the first part devoted to “optimizations”, yet does not provide any speedup numbers. On the GCC side, when asked for numbers, one developer presented numbers he had from some unnamed source from IBM’s XLC compiler, not GCC; these numbers show a speedup factor 1.24 for SPECint from assuming that signed overflow does not happen (i.e., corresponding to the difference between -fwrapv and the default on GCC and Clang). Fortunately, Wang et al. [WCC+12] performed their own experiments compiling SPECint 2006 for AMD64 with both gcc-4.7 and clang-3.1 with default “optimizations” and with those “optimizations” disabled that they could identify, and running the results on a on a Core i7-980. They found speed differences on two out of the twelve SPECint benchmarks: 456.hmmer exhibits a speedup by 1.072 (GCC) or 1.09 (Clang) from assuming that signed overflow does not happen. For 462.libquantum there is a speedup by 1.063 (GCC) or 1.118 (Clang) from assuming that pointers to different types don’t alias. If the other benchmarks don’t show a speed difference, this is an overall SPECint improvement by a factor 1.011 (GCC) or 1.017 (Clang) from “optimizations”. http://www.complang.tuwien.ac.at/kps2015/proceedings/KPS_2015_submission_29.pdf

                                  1. 3

                                    I still have not seen a well worked out example of how UB produces real optimizations

                                    Some examples here (found by a search just now): https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-enables.html

                                    SPECint isn’t necessarily a good way to assess the affect of these particular optimisations.

                                    1. -1

                                      OMG! You find those compelling?

                                      1. 3

                                        OMG! You find those compelling?

                                        Compelling for what? They are examples of how UB produces real optimisations, something that you said you hadn’t seen.

                                        1. 0

                                          They don’t show any real optimization at all. They are micro-optimizations that produce incorrectly operating code.
                                          When the compiler assumes multiplication is commutative and at the same time produces code that has non-commutative multiplication, it’s just terrible engineering. There’s no excuse for that.

                                          As steveklabnik says above “UB has a cost, in that it’s a footgun. If you don’t get much optimization benefit, then you’re introducing a footgun for no benefit.”

                                          1. 3

                                            They don’t show any real optimization at all. They are micro-optimizations

                                            This is self-contradictory, unless by “real optimization” you mean “not-micro-optimization”, in which case you are just moving the goal posts.

                                            that produce incorrectly operating code.

                                            This is plainly false, except if you you mean that the code doesn’t behave as you personally think it should behave in certain cases, even though the language standard clearly says that the behaviour is undefined in those cases. In which case, sure, though I’m not sure why you think your own opinion of what the language semantics should be somehow trumps that of the committee responsible for actually deciding them.

                                            1. 2

                                              Real optimization means substantive. Micro-optimizations like “ we take this arithmetic calculation and replace it with a faster one that produces the wrong answer” are ridiculous.

                                              I’m totally uninterested in this legalist chopping or this absurd deference to a committee which is constantly debating and revisiting its own decisions. It is just absurd for people to argue that the C Standard wording can’t be criticized.

                                              1. 5

                                                “ we take this arithmetic calculation and replace it with a faster one that produces the wrong answer”

                                                And there you have the core of the problem.

                                                You say “wrong answer” implying there is A Right One, as defined by the current C standard sometimes there are no Right Answers to certain operations, only “undefined”.

                                                So Define your Right One, proclaim your New Standard, implement a Conforming compiler and everybody is happy.

                                                I’m entirely on board with you saying the C committee lacks guts to abandon ancient marginal CPU’s and just define things…..

                                                So I look forward to programming in VyoDaiken C.

                                                Alas… I warn you though… you will either end up with something that just isn’t C as we know, or you will still have large areas “undefined”.

                                                1. -1

                                                  There is a right answer. That is the nice thing about arithmetic.

                                                  1. 5

                                                    Machine integers are not a field. Anything we call “arithmetic” on them is defined arbitrarily (usually to be kind of like arithmetic on the mathematical integers, if you ignore the fact that they’re finite), so in fact there’s not a right answer—rather, there are several reasonable ones.

                                                    You could define them to behave however the machine behaves; but this is obviously not consistent from machine to machine. You could define them in a particular way (two’s-complement with signaling overflow, perhaps), but if this definition doesn’t match up to what the target machine actually does, you have potentially expensive checks and special-casing to shore up the mismatch, and you picked it arbitrarily anyway. (Case in point: did you agree with my suggestion? Do you think you could convince all of a room full of C programmers to?)

                                                    There’s a reasonable argument to be made that C should have said integer overflow is implementation-defined rather than undefined, but it’s hard to claim there’s a single obviously correct implementation-independent definition it should have adopted.

                                                    1. 0

                                                      My suggestion is that when you feel like you should tell me something I obviously already know, you should think about what point you are trying to make.

                                                      C has a great deal of room for machine and implementation dependent behavior - necessarily. Implementation defined would have prevented surprises.

                                                    2. 1

                                                      arithmetic.

                                                      Sounds like you’ll love Scheme and Ruby then ;-)

                                                      They have this Numeric Tower concept that does The Right Thing by arithmetic.

                                                      ps: Have a look at this, the ultimate Good News, Bad News story and cry…

                                                      https://lobste.rs/s/azj8ka/proposal_signed_integers_are_two_s#c_xo9ebu

                                                      …if we ever meet in person, we can drown our sorrows together and weep.

                                                  2. 3

                                                    It is just absurd for people to argue that the C Standard wording can’t be criticized.

                                                    Just to be clear, I’m not arguing that (and I don’t think anybody here is doing so). However if you continue to dismiss logical arguments as “legalist chopping”, and suggest that we all defer to you instead of the language committee, I think the discussion will have to end here.

                                                    1. 1

                                                      This is plainly false, except if you you mean that the code doesn’t behave as you personally think it should behave in certain cases, even though the language standard clearly says that the behaviour is undefined in those cases

                                                      I have no idea how to interpret that except as an argument that one is not permitted to question the wisdom of either the standard or the interpretation chosen by compiler writers. As several people on the standards bodies have pointed out, there is certainly no requirement in the standard that compiler developers pick some atrocious meaning. “Optimizing” code that produces a correct result to produce code that does not, produces incorrectly operating code. You can claim that the unoptimized code was in violation of the standard, but it worked.

                                                      Specifically, the example you pointed to starts off by “optimizing” C code that calculates according the mathematical rules and replaces it with code that computes the wrong answer. 2s complement fixed length multiplication is not commutative. Pretending that it is commutative is wrong.

                                                      1. 4

                                                        I have no idea how to interpret that except as an argument that one is not permitted to question the wisdom of either the standard or the interpretation chosen by compiler writers.

                                                        I do not see how pointing out what the language standard does say is the same as saying that you are not permitted to question the wording of the standard. Good day.

                                    2. 2

                                      No. There is nothing about C that requires compilers behave irrationally. The problem is that (1) the standard as written provides a loophole that can be interpreted as permitting irrational compilation and (2) the dominant free software compilers are badly managed ( as someone pointed out, it’s not as if they have paying customers who will select another product). There’s a great example: a GCC UB “optimization” was introduced that broke a lot of code by assuming an out of bound access to an array could not happen. It also broke a benchmark - creating an infinite loop. The GCC developers specifically disabled the optimization for the benchmark but not for other programs. The standard does not require this kind of horrible engineering, but it doesn’t forbid it.

                                      1. 4

                                        There’s a great example: a GCC UB “optimization” was introduced that broke a lot of code by assuming an out of bound access to an array could not happen

                                        You know, I’m quite happy with that.

                                        Every new gcc release comes out with a lot of new optimizations and a lot of new warnings.

                                        Every time I go through our code base cleaning up the warnings, often fixing bugs as I go, I’m ok with that.

                                        Sometimes we only find the borken code on the unit tests or test racks. I’m OK with that. That code was fragile and was going to bite us in the bum sooner or later.

                                        Old working code maybe working, but as soon as you’re into undefined behaviour it’s fragile, and changes in optimization are only one of many ways which can make it break.

                                        In my view, the sooner I find it and fix it the better.

                                        I deliberately wiggle the optimization settings and run tests. If working code breaks… I fix.

                                        I run stuff on CPU’s with different number of cores… that’s a really good one for knocking loose undefined behaviour bugs.

                                        Sure my code “Works for Me” on my desk.

                                        But I don’t control the systems where other people run my code. Thus I want the behaviour of my code to always be defined.

                                        1. 1

                                          I run stuff on CPU’s with different number of cores… that’s a really good one for knocking loose undefined behaviour bugs.

                                          What do you mean by that? Just something like 2 vs 4 or as different as vs a 3 or 6 core that’s not a multiple of 2? Now that you mention it, I wonder if other interaction bugs could show up in SoC’s with mix of high-power and low-energy cores running things interacting. Maybe running on them could expose more bugs, too.

                                          1. 3

                                            The C standard as a bunch of fine grained wording around volatile and atomic and sequence points with undefined behaviour if you understand them wrong.

                                            Threading is dead easy…. any fool can (and many fools do) knock up a multithreaded program in a few minutes.

                                            Getting threading perfectly right seems to be extra-ordinarily hard and I haven’t met anybody who can, nor seen anybodies code that is perfect.

                                            So any fools code will “Work For Them” on their desk… now deploy it on a different CPU with a different number of cores, with a different load ….

                                            ….and the bugs come crawling out in all directions.

                                          2. 1

                                            But of course, there is often no warning.

                                            1. 1

                                              Actually gcc has been remarkably good about this… The last few releases I have dealt with there has a been a pairing of new warnings with new optimization passes.

                                              Which makes sense, because a new optimization pass tends to mean the compiler has learn more about the structure of your code.

                                              Where they have been gravely deficient is with function attribute…. there be dragons and unlikely to get a warning if you screw up.

                                              Curiously enough they will suggest attributes if you haven’t got one… but won;t warn you if you have the wrong one. :-(

                                              Bottom line. Beware of gcc function attributes. They are tricksy and easy to screw up.

                                              1. 0

                                                Are you reading any of the critiques of UB or even the defenses? The core issue is silent transformations of code - e.g. a silent deletion of a null pointer check because UB “can’t happen”.

                                        2. 1

                                          There is no fundamental reason. It’s just a lot of work and no optimizing compiler is implemented that way right now.

                                          1. 0

                                            It looks like part of the problem is an engineering flaw in the compilers where there are multiple optimization passes that can produce errors in combination.