7 ms·
Example of undefined behavior: What arguments will f be called with? int i = 0; f(i++, i++); More examples here: https://www.cl.cam.ac.uk/teaching/1415/
by brianberns 10y ago
Example of undefined behavior: What arguments will f be called with?
int i = 0;
f(i++, i++);
More examples here: https://www.cl.cam.ac.uk/teaching/1415/CandC++/lecture10.pdf https://www.cl.cam.ac.uk/teaching/1415/CandC++/lecture10.pdf
- gravypod 10y agoI'd assume it would be f(1, 0); Because function arguments are pushed in reverse order in C and funnily enough, GCC actually does that even though that's crazy. But clang catches it as an "unsequenced modification". I don't think that's a good example. The C standards make no guarantees about unsequenced modifications so relying on them is the programmer not using C, it's the programmer using a language derivative implemented by their compiler. Why do people say "C remains central to our computing infrastructure but still lacks a clear and complete semantics" when the standards are very clear about what it allows? If you use something it doesn't allow you can't blame the spec for that.
- brianberns 10y agoI don't understand why you say that it's not a good example. It's legal C that has undefined semantics, which is what you were asking for.
- gravypod 10y ago> It's legal C No it's not. It's undefined behavior meaning, by definition, it's not C. It's not defined in C. If I had an list of all definitions, would an undefined item be in my list? No. When you are using Undefined Behavior, you are not using C. You're using a C-extension that a compiler designer has implemented. This was done because there is no sane operation to do in these UB cases for one reason or another. Either it would not be portable or it wouldn't make sense. If there was a standard that implemented standards for UB cases it would not be portable.
- brianberns 10y agoOK, so to paraphrase your position: C has clear and complete semantics because anything written in C that doesn't have clear and complete semantics is, by definition, not actually written in C. Yes? To me, that's like saying the tax code doesn't contain any loopholes, because any loopholes that it contains are, by definition, not actually part of the tax code. While amusing, I don't think this is a fruitful way to look at the problem. :)
- loeg 10y agoYou can write things that look like C, but that doesn't make them valid C. Invalid code is just a collection of ASCII characters, I guess.
- pasquinelli 10y agoDoes the parser reject it? That's the test for syntactically valid c.
- loeg 10y agoSyntactically valid is not a perfect match for valid.
- marvy 10y agoI think this answers your earlier question. We don't have tools that implement "C". We only have tools that implement supersets of C. In particular, these tools take code like the above and do "something" with them; the exact thing they do varies with optimization level and phase of the moon. What you want is a tool that takes such code and says "Oops, undefined behavior on line 9, here's a stack trace."
- chaoky 10y agoI think you're confusing syntax and semantics. The claim is that C is not really semantically well defined, but your toplevel posts talk about the fact that C has well defined syntax, which is true. However, that's besides the point. Syntax is roughly the surface form, whereas semantics refers to meaning. The problem is that a well formed program (ie a syntactically correct program; this is actually the precise definition of well-formed) in C yields undefined behavior on a semantic level, exactly what we don't want to happen. You need to maintain a clear separation between the syntax and meaning. When parent said that its legal C, he meant syntactically. The point is by definition you cannot tell from the surface/lexical form of C what is semantically valid and what is not semantically valid, ie what is well defined and what is not. So by saying its undefined behavior you are proving parent's point, that a syntactically valid piece of C yields a semantically not well defined chunk of code.
- CalChris 10y agoWhat if f is a macro. Then it has a lexically determined meaning. What if f is a function. Then it has a parsing determined meaning. What if the ++ operator has been redefined. Et cetera.
- gravypod 10y agoIf F is a macro then it will just be expanded. Lets say F was #define f(a, b) printf("%d %d\n", a, b); This would change absolutely nothing. A macro is just a piece of code that is expanded. It still follows all of the rules (and disallowances) of C.
- foota 10y agoFairly certain you can have compound statements as the result of the macro. e.g., the macro could expand to if(a) {printf("%d\n", b);} for example
- Normal_gaussian 10y agoYou can. Of course you don't even need the logical connector #define f(a,b) \ f_var1 = a; \ f_var2 = b Equally you could use macros to make it a whole lot worse #define f(a,b) \ f_arr[a][b] += f_arr[b][a]
- snovv_crash 10y agoWhat if the macro uses a and/or b more than once?
- loeg 10y agoAt least, the ++ operator cannot be redefined in C.
- tathougies 10y agoThe ability to redefine the ++ operator would not alone make it difficult to add proper semantics to the C language. What is preventing that is that no one can really describe how function application truly works in C (in terms of operation ordering), except that we all have an idea similar enough that our systems seem to work together in practice. For all the criticism Haskell gets over the purported difficulty on describing sequential computation, at least the order of monadic operations is clearly captured by the requirements to be a monad. There is no such concept for C, except that each compiler mostly seems to perform operations sequentially.
- laughinghan 10y agoYou're missing the point. Can we agree that people are learning a programming language that they call "C", and writing text files in that language and running programs that they call "C compilers" such as gcc on those text files to produce binary executables? You are correct to note that the programming language I'm describing does not perfectly conform to any of the documents that are ever called a "C standard". But that programming language is central to our computing infrastructure, and whether you or C standard authors like it or not that programming language is widely called "C". That language's semantics are unclear and incomplete---even though both gcc and clang are commonly called "C compilers", they have differing behavior on `f(i++, i++)` as you found; reportedly, there's code on which gcc behaves differently depending on the optimization level option.
- zodiac 10y ago> You are correct to note that the programming language I'm describing does not perfectly conform to any of the documents that are ever called a "C standard". But that programming language is central to our computing infrastructure I don't agree with this. Yes, people often write code that unintentionally rely on UB/implementation-specific behavior; but are there important projects that do this intentionally? In any case, compiler authors are going to follow the C standards, not you hypothetical "C-in-practice" standards, so what good does it do to for formalize or clearly specify the latter?
- jabot 10y ago> Because function arguments are pushed in reverse order in C and funnily enough, GCC actually does that even though that's crazy. Actually, pushing function arguments right-to-left means that the first argument ends up on top of the stack - and that makes vararg-style functions possible. Think about it - a called vararg method must be able find the fixed arguments to figure out how many arguments it actually gets.
- gravypod 10y agoThat's what I mean by "Pushed in reverse order"
- nickpsecurity 10y agoWhile people are arguing it's syntax, you all might enjoy this parody that's a good example of Poe's Law due to C's horrid (i.e. lack of a) design: https://www.gnu.org/fun/jokes/unix-hoax.html https://www.gnu.org/fun/jokes/unix-hoax.html
- danieltillett 10y agoIs this example non-deterministic or just undefined? Sure you should not rely on this code to give you consistent results between compilers and even optimisation levels, but what ever happens should be consistent between runs.
- gravypod 10y agoThis is UB and it's caught by both GCC and Clang: $ gcc -Wall test.c test.c: In function ‘main’: test.c:12:13: warning: operation on ‘a’ may be undefined [-Wsequence-point] test(a++, a++); ~^~ $ clang test.c test.c:12:8: warning: multiple unsequenced modifications to 'a' [-Wunsequenced] test(a++, a++); ^ ~~ 1 warning generated.
- danieltillett 10y agoExactly. The OP asked for an example of a non- deterministic function not UB.
- fmap 10y agoI'm not sure about non-deterministic behavior without looking it up, but you can fix this example to be implementation defined, rather than undefined like this: #include <stdio.h> int postincrement(int *x) { int y = *x; *x = y + 1; return y; } int main(int argc, char **argv) { int i = 0; printf("%d, %d\n", postincrement(&i), postincrement(&i)); return 0; } The point is that function arguments introduce sequence points, but the specification for i++ is weaker than what you get for a function call. C is full of gotchas like this...
- pasquinelli 10y agoTheir term was non-deterministic, but they asked for undefined behavior, because they were asking for examples of course being not fully defined semantically.
- Keyframe 10y agoExactly! Without sequence-point error, output is 1,0 on GCC6, 0,1 with CLang.
- agumonkey 10y agoI'm dumbfounded by all the operators of C that enable all this confusion. About the potential order of evaluation, it's not limited to C, but at least it's clearly and dilligently explained even in tiny standards like Scheme.
- Johnny_Brahms 10y agoThe scheme standard actually bit my sorry behind recently. I was porting a small library between scheme implementations, and my tests were failing. I had always thought it was evaluated left->right (which it was in the first scheme implementation), but it is in fact standardised as "The order of evaluation is unspecified". The reason I hadn't noticed before was that most my code is rather functional. Now I was reading a char from a file handle, and peeking it as the other argument. It failed because read-char is destructive (it changes the position of the file pointer), and since the order was the other way around in the scheme I was porting to, all the procedures relying on my "read-until" method started failing left and right. It just took me ~20 years to figure it out...
- agumonkey 10y agoouch.
- sigjuice 10y agoWould this be an issue in Common Lisp? I believe CL function arguments are evaluated left-to-right.
- lispm 10y agoThat's right. The standard says left to right. It's not good style to exploit that, but you can.
- fiddlerwoaroof 10y agoIt is undefined in Common Lisp whether or not the car of a form is resolved to a function before or after the other arguments, iirc