1. 22
  1.  

  2. 4

    What an honor to see that someone else posts my post! I wrote this because of this post on lobste.rs.

    1. 3

      Great article! I noticed you mentioned that f(sleep(7)) is legal but not why. This is described in 6.5.2: http://port70.net/~nsz/c/c99/n1256.html#6.5.2.2p6

      If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined.

      Notice how it never mentions how to determine the number of parameters (clearly you can’t find it from the prototype). This is a concession to pre-C89 code, which did not have function prototypes at all! I actually tried to compile some of my professor’s code from 1989 which was using this feature a while back.

      Some more discussion of this misfeature here: https://github.com/jyn514/rcc/issues/61

      1. 2

        Wow, what an amazing contribution, thank you!

        Does an integer promotion then also mean that the argument is automatically executed?

        1. 3

          I am not sure what you mean by automatically executed. The arguments to a function are evaluated in an unspecified order before the function is called, but integer promotion means something different: types are promoted to int or unsigned int before being passed, as per 6.3.1.1 (http://port70.net/~nsz/c/c99/n1256.html#6.3.1.1p2):

          If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.48) All other types are unchanged by the integer promotions.

          1. 2

            The arguments to a function are evaluated in an unspecified order before the function is called, […]

            I was actually referring to this when I said executed. Thank you!

      2. -1

        According to section 6.2.5.12, integers are arithmetic types. This, in combination with the second rule, makes that i will now be 0.

        But the text you cite says: “If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static storage duration is not initialized explicitly, then:”. I fail to see how i; is equivalent to static i;. As far as I can tell, i; being initialized to zero just so happened to be done by the compiler and/or resident memory, conveniently, but there’s no actual guarantee of that.

        Plus i is implicitly (signed) int, so --i; is signed integer overflow, and also well into undefined behavior territory. I’d imagine a compiler at would be well in its rights to just optimize the entire function to nothing because UB occurs first thing, since once you hit UB, all bets are off.

        1. 6

          i has external linkage, and static storage duration.

          C99 6.2.2p5

          If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

          C99 6.2.4p3

          An object whose identifier is declared with external or internal linkage, or with the storage-class specifier static has static storage duration. Its lifetime is the entire execution of the program and its stored value is initialized only once, prior to program startup.

          The static storage-class specifier means that the linkage is internal or none, depending on whether the declaration is at file scope or block scope, and the storage duration is static. Objects with external linkage (for example extern int i;), or no linkage (for example static int i; at block scope), also have static storage duration.

          1. 3

            Thank you so much for taking the time to look up the relevant pieces in the standard!

            1. 5

              No problem :) I spent a long time studying these pieces of the standard when writing cproc, and I know how tricky they are.

            2. 2

              Drats, I actually got out-language-lawyered. Learned something new, cheers!

              1. 2

                Great link to an HTML version of the standard, I’ve been using the PDF and it’s much harder to navigate. I’m very impressed by your compiler too, it’s much further along than mine: https://github.com/jyn514/rcc.

                I noticed your compiler is a little inconsistent about functions without prototypes:

                $ ./cproc-qbe
                int f() { return 0; }
                int main() { f(1); }
                export
                function w $f() {
                @start.1
                @body.2
                	ret 0
                }
                <stdin>:2:17: error: too many arguments for function call
                $ ./cproc-qbe
                int f();
                int main() { return f(1); }
                export
                function w $main() {
                @start.1
                @body.2
                	%.1 =w call $f(w 1)
                	ret %.1
                }
                
                1. 3

                  The difference between

                  int f();
                  

                  and

                  int f() { return 0; }
                  

                  is that the first declaration specifies no information about the parameters, and the second specifies that the function has no parameters. When calling a function, the number of parameters must match the number of arguments, so I believe the error message is correct here.

                  C99 6.7.5.3p14

                  An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

                  C99 6.5.2.2p6

                  If the number of arguments does not equal the number of parameters, the behavior is undefined.

                  I’m very glad C2X is removing function definitions with identifier lists (n2432). So int f() { return 0; } will actually be the same thing as int f(void) { return 0; }.

                  1. 3

                    I missed 6.7, thank you! That makes things easier for me to implement I think :)

              2. 4

                I am always afraid to answer to detailed questions like these, because I am still learning a lot about C and not always sure. However, I believe that i actually has a automatic static storage duration, because it is defined outside of the scope of a function or whatsoever. This means that the variable will be persistent throughout the whole program.

                Edit: I said that i would have an automatic storage duration, but the arguments are for a static storage duration. This is what I actually meant to write. My apologies for the inconvenience.

                The second point is a good one, about which I responded earlier in a comment under my post. You depend on your compiler for that indeed. This line was written for gcc specifically however.

                1. 0

                  I am always afraid to answer to detailed questions like these, because I am still learning a lot about C and not always sure.

                  Very few people actually know C. Given that writing it is actually just an extremely elaborate exercise in language lawyering, it truly is a language only a lawyer could love. The worst that could happen is that you get corrected if you give a wrong response—meaning that you’d learn something from it.

                  However, I believe that i actually has an automatic storage duration, because it is defined outside of the scope of a function or whatsoever. This means that the variable will be persistent throughout the whole program.

                  Indeed so, that’s my understanding as well. But that also means that its initial value is indeterminate: Automatic storage donation is mutually exclusive with static storage donation. Therefore, you cannot actually get the zero-initialization you’d get from static storage duration, and instead the value is indeterminate because it has automatic storage donation.

                  1. 6

                    Every object declared at file scope has static storage duration. The only objects that have automatic storage duration are those declared at block scope (inside a function) that don’t have the storage-class specifier static or extern.

                    1. 3

                      The worst that could happen is that you get corrected if you give a wrong response—meaning that you’d learn something from it.

                      I agree, which is why I always answer indeed. Thank you.

                      Indeed so, that’s my understanding as well. But that also means that its initial value is indeterminate: Automatic storage donation is mutually exclusive with static storage donation.

                      I changed my post above. I made a mistake while writing that, due to my lack of time. The fact that i is defined outside of the scope of a function, means that it is static, as mcf described