As already been said (and as you already know), your code isn't right and it will cause an undefined behaviour (which means it'll produce different results, or may be an exception, depending on the implementation and/or the platform).
You want to know WHY it works in your case. Without knowing the specifics, we can only guess. But I'm pretty sure it's because the way parameters and return values are internally handled.
When using the "C calling convention" (edited, see the comments below), actual parameters are placed by the calling function into the stack. The called function reads these parameters but doesn't adjust the stack pointer. After the called function has returned, the calling function adjusts the stack pointer ("removing" the parameters from the stack). (Other calling conventions do things in a different way. In Pascal, for instance, is usually the calle responsibility to clear the stack.) Doesn't matter if the caller and the calle are the same function (that is, if the share the same code).
But the return value is handled differently; and, if I remember well, that's not defined by the specification. Usually, the return value is just "passed up" to the calling function using a CPU register (R0, AX, etc) (this is usually dependant on the calling convention of the platform, if it has one).
In your case, the only return value that matters is the last one. (Actually, that's your only return value, the one when (n == 0), because your code is not recursive in the usual way. It doesn't depend on the return value of each "internal" call. it just uses recursion for adding up the curr_sum. About that, check @stark comment.)
Then, when the recursive function hits (n == 0), it puts the cumulated curr_sum into its target destination (let's say it's register R0) and begins the unwinding. Each subsequent return will just do that: return to the calling function, without overwriting the "result" stored in R0. Finally, the main function will read this result, assuming it's from the function it directly called, the first iteration of sum_up_to_n().
Also, the compiler may be avoiding the (in your case) unneeded recursion and may be doing some kind of optimization, converting it into a simple loop.
sum_up_to_nwithout areturn(which happens when n != 0) causes undefined behavior. My compiler (MSVC) gives a warning: warning C4715: 'sum_up_to_n': not all control paths return a value-Wall -Wextra -pedanticsum_up_to_ncould pick up anything. Bad code.return n*(n+1)/2;without any recursion.