-2

The following code is here with keep the C Language Syntax:

#include <stdio.h>
int func(int a, int b){
if (b==0)
return 0;
else return func(a,b);
}

int main(){
printf("%d \n", func(func(1,1),func(0,0)));
return 0;
}

What is the output of this code at 1) run with standard C, 2) with any language that has call by need property, Then:

in (1) the programs loop into infinite call and in (2) we have ouptut zero !! this is an example solved by TA in programming language course, any idea to describe it for me? thanks

sepp2k
  • 363,768
  • 54
  • 674
  • 675

3 Answers3

2

1) In C (which uses strict evaluation semantics) we get infinite recursion because in strict evaluation arguments are evaluated before a function is called. So in f(f(1,1), f(0,0)) f(1,1) and f(0,0) are evaluated before the outer f (which one of the two arguments is evaluated first is unspecified in C, but that does not matter). And since f(1,1) causes infinite recursion, we get infinite recursion.

2) In a language using non-strict evaluation (be it call-by-name or call-by-need) arguments are substituted into the function body unevaluated and are only evaluated when and if they're needed. So the outer call to f is evaluated first as such:

if (f(0, 0) == 0)
return 0;
else return f(f(1,1), f(0,0));

So when evaluating the if, we need to evaluate f(0,0), which simply evaluates to 0. So we go into the then-branch of the if and never execute the else-branch. Since all calls to f are only used in the else-branch, they're never needed and thus never evaluated. So there's no recursion, infinite or otherwise, and we just get 0.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
-1

To address the first part,

The C Draft, 6.5.2.2->10(Function calls) says

The order of evaluation of ... the actual arguments... is unspecified.

and for such reason, something such as

printf("%d%d",i++,++i); 

has undefined behaviour, because

  • both ++i and i++ has side-effects, ie incrementing the value of i by one.
  • The comma inside printf is just a separator and NOT a [ sequence point ].
  • Even though function call itself is a sequence point, for the above reason, the order in which two modifications of i take place is not defined.

In your case

func(func(1,1),func(0,0))

though, the arguments for outer func ie func(1,1) or func(0,0) have no bearing on each other contrary to the case shown above. Any order of evaluation of these arguments eventually leads to infinite recursion and so the program crashes due to depleted memory.

sjsam
  • 21,411
  • 5
  • 55
  • 102
  • 2
    What do you mean by "It is upto the processor to chose the order of the function arguments"? Why is it UB? – Xiobiq Jul 27 '16 at 17:47
  • I know, but why do you think it has undefined behavior? –  Jul 27 '16 at 17:48
  • 2
    Either `func(1,1)` or `func(0,0)` could be evaluated first... but since both have to be evaluated before the outer `func()` call, it'll still give infinite recursion (in C). – Dmitri Jul 27 '16 at 17:49
  • 3
    @Dmitri I think in standard "C" we have infinite loop, but not sure about the second part –  Jul 27 '16 at 17:50
  • This is infinite recursion which will lead your program to crash, can't see why this is UB. – Xiobiq Jul 27 '16 at 17:52
  • No UB here, unless we consider stack overflow due to infinite recursion as UB. This looks like a wrong answer. Both parameters *are* evaluated upon entering the function according to C standard. – Eugene Sh. Jul 27 '16 at 17:53
  • This is clearly not UB. This is unspecified, that just means it is up to the compiler and you can't rely on the order of evaluation. It also doesn't make any sense that it would be UB. this is perfectly defined. – Xiobiq Jul 27 '16 at 17:56
  • @Polikdir It's not even unspecified. It is well defined. – Eugene Sh. Jul 27 '16 at 17:56
  • @Polikdir : Unspecified means one cannot guarantee consistent results imho, so undefined behaviour. – sjsam Jul 27 '16 at 17:57
  • @EugeneSh. He was talking about the order of evaluation (which is unspecified acording to his quote) of the arguments, I commented on that. My last sentence on that comment also states (about the whole code) that this is perfectly defined :) – Xiobiq Jul 27 '16 at 17:58
  • 2
    @sjsam *All of the parameters are evaluated prior entering the function*. Function call is a sequence point. The function doesn't have side effects, so the order doesn't matter. *No UB here*. Everything is well specified and defined. – Eugene Sh. Jul 27 '16 at 17:59
  • @EugeneSh : Indeed i thought of this in terms of function side-effects(none here). I stand corrected. :) Will edit shortly. – sjsam Jul 27 '16 at 18:06
  • 2
    You don't seem to address the second part of the question (call-by-need) at all. – sepp2k Jul 27 '16 at 18:10
  • Sorry @Polikdir what do you mean by UB? –  Jul 27 '16 at 18:12
  • 1
    @SaraPhD *UB* is commonly used on SO to refer *undefined behavior*. – Eugene Sh. Jul 27 '16 at 18:13
  • @EugeneSh. thanks so much. but I think UB is not related to infinite recursive calls... –  Jul 27 '16 at 18:16
  • @SaraPhD It doesn't. It's consequences might indirectly relate. But your question is not about it. – Eugene Sh. Jul 27 '16 at 18:18
  • @sepp2k : I haven't come across, `call by need`. Your answer already addressed it, it seems :) – sjsam Jul 27 '16 at 18:58
  • "Any order of evaluation of these arguments eventually leads to infinite recursion and so the program crashes due to depleted memory." Unless your compiler does tail-call optimization :) – TLW Jul 27 '16 at 22:08
-1

With C, in general, it is not defined the order of arguments a and b evaluation with a function like int func(int a, int b)

Obviously evaluating func(1,1) is problematic and the code suffers from that regardless if func(1,1) is evaluated before/after/simultaneous with func(0,0)


Analysis of func(a,b) based on need may conclude that if b==0, no need to call func() and then replace with 0.

printf("%d \n", func(func(1,1),func(0,0)));
// functionally then becomes
printf("%d \n", func(func(1,1),0));

Applied again and

// functionally then becomes
printf("%d \n", 0);

Of course this conclusion is not certain as the analysis of b != 0 and else return func(a,b); leads to infinite recursion. Such code may have a useful desired side-effect (e.g. stack-overflow and system reset.) So the analysis may need to be conservative and not assume func(1,1) will ever return and not optimize out the call even if it optimized out the func(0,0) call.


chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • So you means when we run it on standard C because of evaluation before/after/simultaneous of argument, the behavior is undefined? –  Jul 27 '16 at 18:16
  • @Sara PhD C does not defined the upper limit of recursion(it could be infinite (stack frame re-used)), only that at least a minimum amount or recursion possible. With `int main() { while (1); }`, we do not have undefined behavior UB. It just never completes. Like-wise, code or may/may not may hit a limit. – chux - Reinstate Monica Jul 27 '16 at 18:21
  • Semantically the C program will just run for infinite time. In practice - we don't know. does it count undefined behavior? – Eugene Sh. Jul 27 '16 at 18:24
  • @Eugene Sh. The infinite time issue does not make it UB. Too many recursions likely do - looking for cite. – chux - Reinstate Monica Jul 27 '16 at 18:25
  • @chux It's unlikely you will find it, as C standard doesn't even have a notion of stack.. – Eugene Sh. Jul 27 '16 at 18:26
  • @Eugene Sh. I was not looking for a C spec concerning "stack", only recursion limit. http://stackoverflow.com/a/10243313/2410359 does address this a bit. – chux - Reinstate Monica Jul 27 '16 at 18:28
  • I read all the comments again and your answer, can I conclude in (1) we encounter to infinite recursive call, in (2) the function print 0. isnt it? –  Jul 27 '16 at 18:30
  • @SaraPhD That's the bottom line. Do you understand the reasoning? – Eugene Sh. Jul 27 '16 at 18:32
  • @Sara PhD (note: I am not finding a _minimum_ supported recursion depth either.) With 1) without analyzing the function, we have infinite recursion. With 2) it depends on how smart an analysis and if infinite recursion can be consider an ignorable side effect. As I see it it is not, so with 2) `func(0,0)` becomes 0, but `func(1,1)` still needs to be called and a return is not expected. So a real good analyzer could replace the entire code with `int main() { while (1); }` – chux - Reinstate Monica Jul 27 '16 at 18:34
  • Not all, @EugeneSh. would you please add detail in one comment? I confused a litle. –  Jul 27 '16 at 18:35
  • @chux really this is a Olympiad Question that took 3 month ago, answer sheet says in (1) we have infinite recursive calls, in (2) we have zero output. the answer sheet is written by TA. –  Jul 27 '16 at 18:37
  • @SaraPhD Do you understand why *mathematically* the result of such a function is 0? – Eugene Sh. Jul 27 '16 at 18:37
  • @SaraPhD From a quasi-math/programming point-of-view, in (2) we have zero output, _after an infinite amount of time_. – chux - Reinstate Monica Jul 27 '16 at 18:39
  • is there any link to study for quasi-math/orogramming point-of-view? –  Jul 27 '16 at 18:40
  • @chux Why "after an infinite amount of time"? How isn't the result just 0 in O(1) time using call-by-need evaluation? – sepp2k Jul 27 '16 at 18:41
  • @SaraPhD Because when looking at `func(func(1,1),func(0,0))` we can first evaluate `func(0,0)` which is `0`, as `b=0`. So the call is transforming into `func(func(1,1),0)`. Here `b` of the outer call is `0` as well, which is sufficient to conclude the final result is zero, without even evaluating `func(1,1)` – Eugene Sh. Jul 27 '16 at 18:47
  • @sepp2k Analysis of `func(1,1)` has the recursive call of `func(1,1)`. Good code analysis will not exclude code that has _side-effects_ of unknown impact. Infinite recursion of `func(1,1)` has a side effect of _unknown_ consequence, thus it _should not_ be ignored. Replace `else return func(a,b);` with `else { putchar('x'); return func(a,b);}`. Certainly code cannot ignore the printing of `x`. I suspect TA hand-waved that path as UB and reasoned anything goes, including ignoring it. I disagree. – chux - Reinstate Monica Jul 27 '16 at 18:49
  • @chux A lazy-evaluating compiler/interpreter given equivalent sequence will eventually terminate with a correct result, as `b` is checked *before* the recursive call. Languages like Haskell are relying on this here and there. – Eugene Sh. Jul 27 '16 at 18:52
  • 1
    @chux I don't think you understand what call-by-need is. – sepp2k Jul 27 '16 at 18:53
  • `call by need` is rather uncommon term. `Lazy evaluation` is the more common one.. – Eugene Sh. Jul 27 '16 at 19:02
  • @sepp2k [Be nice](http://stackoverflow.com/help/be-nice) Rather than [comment on the person](http://stackoverflow.com/questions/38619703/call-by-need-and-standard-c-output/38620290?noredirect=1#comment64627813_38620290), better for comments/answers to focus on the comments/answers. – chux - Reinstate Monica Jul 27 '16 at 19:07
  • @chux Sorry if I offended you. Your comment explaining why it'd take infinite time to evaluate using call-by-need semantics, actually used strict semantics (evaluating the arguments first) and thus does not seem applicable to call-by-need. – sepp2k Jul 27 '16 at 19:16
  • @sepp2k I suspect we are using different understandings of the implementation limits of _call by need_. Since "call-by-need" is not defined in `C`, as a C post, we are not contained to a single interpretation. As this answer asserts, the infinite recursion of `func(1,1)` is not certainly UB and its side effect might not be ignorable. Not much different from code `else while (1);` – chux - Reinstate Monica Jul 27 '16 at 19:28
  • @chux (1) is a C question. (2) is about "any language that has call by need property" and thus not about C, so the fact that C has no concept of call-by-need does not matter with regards to (2). – sepp2k Jul 27 '16 at 19:42
  • @sepp2k C allows analyzabilty C11dr §L and so is not certainly excluded from optimizations using _call-by-need_. This answer focuses within that context of how call-by-need applies to C. Within a _any language_ universe, we do not have a specification in order to arbitrate, so a correct call-by-need interpretation is subjective. – chux - Reinstate Monica Jul 27 '16 at 20:01
  • @chux It is not subjective. As the name suggesting, it is the evaluation all and only the path required for obtaining the final result. But the point is that even the languages using the lazy evaluation, such as Haskell won't be able to do it if the program is not written in the right order of evaluation. Here, if we assume the right-to-left order, the program will terminate. If we assume left-to-right, it won't. (I suspect the true *call-by-need* evaluation is an undecidable problem). – Eugene Sh. Jul 27 '16 at 20:07
  • @chux call-by-need isn't an optimization. It's a different evaluation order, it's different semantics. And this isn't a C question. C was just used as an example of a strict language. (2) is specifically not about C. – sepp2k Jul 27 '16 at 20:15
  • @sepp2k Concerning "this isn't a C question". Post is tagged C. So it certainly appears OP thinks it is a C question. Suggest telling OP the tag is wrong then. Sample codes does compile as C. Call-by-need can be used for optimization in C, given that it does not violate C specification. – chux - Reinstate Monica Jul 27 '16 at 20:24
  • @sepp2k Concerning 2) we are going over old ground. Post does not specifically exclude C. Instead OP "2) with any language that has call by need property" and I maintain, using C and analyzability, code can optimize using _call-by-need_ (within limits) – chux - Reinstate Monica Jul 27 '16 at 20:28
  • @chux It's tagged as C because part (1) is about C (though it could just as well have been about any other strict language with similar syntax). (2) is not. The question is specifically about the difference between C (or strict languages in general) and languages with call-by-need semantics. And no, call-by-need is not an optimization. It's an evaluation order that is semantically different from strict semantics. You can have an optimization that acts like call-by-need in those cases where the semantics are the same (as-if rule), but this question is about a case where they're not. – sepp2k Jul 27 '16 at 20:30
  • @chux Call-by-need is a strongly normalizing evaluation order. C is not strongly normalizing (otherwise the given code would not cause infinite recursion). Therefore C is obviously not a call-by-need language. You can't call it call-by-need because it might act like call-by-need in cases where the semantics are the same. A call-by-need language is one that acts like call-by-need particularly in those cases where the semantics differ. – sepp2k Jul 27 '16 at 20:32
  • @sepp2k Good we agree on "can have an optimization that acts like call-by-need...". – chux - Reinstate Monica Jul 27 '16 at 20:34