Live data from Hacker News

There is no 'printf'

netmeister.org

31–40 of 158 posts

Re: There is no 'printf'

#31

Imagine somebody thought omitting the return statement and doing whatever the compiler likes is a good feature to have.

Like Scala?

pretty sure scala (and most FP) has a well-defined "what to do when you leave off the return statement", not one that "is up to the compiler"

Re: There is no 'printf'

#32

Moar please. I'm loving these counterintuitive C optimization gotchas lately[1]. They are like little brain teasers. 1. https://news.ycombinator.com/item?id=28930271

About a year ago there was something of a "joke isEven() implementation discourse" on Twitter, which eventually evolved a sort of informal optimizer abuse contest. For example: https://twitter.com/zeuxcg/status/1291872698453258241 https://twitter.com/jckarter/status/1428071485827022849

OK, those are horrifying and fascinating, and they basically break my brain.

Is there a explanation somewhere of why the first one "works"? The second one I think is the compiler assuming the default case will never be hit since it'll result in infinite recursion, which is UB under C++, so it's basically assuming 0The first one I'm less certain about. The only thing I can think of is that the compiler deduces an upper limit of INT_MAX - 1 to avoid signed overflow, and then somehow figuring out the true/false pattern from there? Still a bit of a gap in my understanding there.

Re: There is no 'printf'

#33
post #18

Earlier quoted context omitted.

Per the man: > puts() and fputs() return a nonnegative number on success, or EOF on error. r is the result of the write, if it’s nonzero the write failed and thus so did puts.

Yeah, but I think the question was why EOF and "\n". It could as easily just return 1 or -1 for example, and it would make more sense I think.

puts() always adds a line termination so success means that '\n' is the last char for that implementation.

Re: There is no 'printf'

#34

This is a bit like saying there is no '+'; Because if you put in return 1+2+3; And look at the assembly code, you will see that the compiler generated something like return 6; The compiler is allowed to take advantage of the standard to substitute in more efficient code that does the same thing. IIRC, for C++, it would actually be ok if std::vector was implemented completely as a compiler intrinsic with no actual hea…

yeah but everyone knows that "there is no +"; It's an operator, and in C, anyways operators are special and expected to not necessarily do C-function-ey things, e.g, "take arguments of different types and add them successfully" not everyone is aware that C has "anointed functions" (including, I believe malloc) that the compiler is allowed to fiddle with.

Re: There is no 'printf'

#35

Earlier quoted context omitted.

> more efficient code that does the same thing In this case, it produces a different result.

It produces a different ub, which is ub. Furthermore observability would be defined in terms of the C abstract machine, “observing” by decompiling the program is out of scope.

oh right

> But what if you're not using C99 or newer?

UB - that takes all the fun out of it.

Re: There is no 'printf'

#36
There is 'printf'. It's just that printf (and the rest of the standard library) is technically as much a part of the C language as the language grammar itself, and C compilers are welcome to use innate knowledge of those functions for optimizations. The other place you typically see this is calls to functions like memcpy/memset being elided to inline vector ops or CISC copies, or on simpler systems, large manual zeroing and copying being elided the other way to a memset or memcpy call.

C compilers will typically have an escape hatch for envs like deeply embedded systems and kernels like gcc's -ffreestanding and -fno-builtin that says "but for real though, don't assume std lib functions exist or you know what they are based on the function's name".

One of my favorite parts of rust as someone who uses it for deeply embedded systems is the separation of core and std (where core is the subset of std that only requires memcpy, memset, and one other I'm forgetting). The rest of the standard library is ultimately an optional part of the language with compiler optimizations focused on general benefits rather than knowing at the complier how something like printf works. no_std is such a nicer env than the half done ports of newlib or pdclib that everyone uses in C embedded land.

Re: There is no 'printf'

#37

Quick Summary: The C compiler optimizer replaces printf("Hello World!\n") with puts("Hello World!\n") and the implicit return from main() changes from 13 (the return value of printf) to 10 (the return value of puts)

Calls on puts you say?

Re: There is no 'printf'

#38

Earlier quoted context omitted.

About a year ago there was something of a "joke isEven() implementation discourse" on Twitter, which eventually evolved a sort of informal optimizer abuse contest. For example: https://twitter.com/zeuxcg/status/1291872698453258241 https://twitter.com/jckarter/status/1428071485827022849

OK, those are horrifying and fascinating, and they basically break my brain. Is there a explanation somewhere of why the first one "works"? The second one I think is the compiler assuming the default case will never be hit since it'll result in infinite recursion, which is UB under C++, so it's basically assuming 0 The first one I'm less certain about. The only thing I can think of is that the compiler deduces an upp…

My guess: since overflowing int is UB, and the only value of n that stops the recursion is zero, the compiler assumes that n must be zero and checks accordingly.

That doesn’t explain why it uses test dil, 1 instead of test dil, dil or cmp 0 or whatever.

Re: There is no 'printf'

#39

Quick Summary: The C compiler optimizer replaces printf("Hello World!\n") with puts("Hello World!\n") and the implicit return from main() changes from 13 (the return value of printf) to 10 (the return value of puts)

Calls on puts you say?

Brilliant.

Re: There is no 'printf'

#40

Earlier quoted context omitted.

About a year ago there was something of a "joke isEven() implementation discourse" on Twitter, which eventually evolved a sort of informal optimizer abuse contest. For example: https://twitter.com/zeuxcg/status/1291872698453258241 https://twitter.com/jckarter/status/1428071485827022849

OK, those are horrifying and fascinating, and they basically break my brain. Is there a explanation somewhere of why the first one "works"? The second one I think is the compiler assuming the default case will never be hit since it'll result in infinite recursion, which is UB under C++, so it's basically assuming 0 The first one I'm less certain about. The only thing I can think of is that the compiler deduces an upp…

Optimizers have to keep the same input/output pairs unless there is undefined behavior. In the second function the truth table looks like:

    in    | out
    ----------
    0b000 | 1
    0b001 | 0
    0b010 | 1
    0b011 | 0
    0b100 | don't care
          .
          .
          .
    MAX   | don't care
The compiler just chooses the most efficient way it knows to get the filled out entries correct which happens to be:

    in    | ~in[0]
    ----------
    0b000 | 1
    0b001 | 0
    0b010 | 1
    0b011 | 0
    0b100 | 1
          .
          .
          .
    MAX   | 1
It would have been just as valid to do:

    in    | in[2] or ~in[0]
    ----------
    0b000 | 1
    0b001 | 0
    0b010 | 1
    0b011 | 0
    0b100 | 1
    0b101 | 1
          .
          .
          .
    MAX   | 1
The first function's table looks like:

    in    | out
    ----------
    0b000 | 1
    0b001 | don't care
    0b010 | don't care
          .
          .
          .
    MAX   | don't care
And the compiler still likes the even check in this case, which makes sense.
Post reply on HN