Live data from Hacker News

I Do Not Know C: Short quiz on undefined behavior (2015)

kukuruku.co

101–110 of 189 posts

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#101
I feel bad because I'm smart enough to answer these questions correctly in a quiz format, but if I saw any of them in production code, I would not even think twice about it.

(the quiz questions themselves lead you on, plus I read the MIT paper on undefined behavior that was posted on here back in 2013)

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#102

Earlier quoted context omitted.

Compiler writers have to use program transformation to do well on benchmarks. Developers who don't prioritize benchmarks probably don't use C, and if they do they really shouldn't, because sacrificing correctness for speed is the only thing C is good for these days.

In reality, that's why I think that neither Go nor Rust will ever replace C (Maybe C++, Java or Python), because nothing can replace C. C worked in the 70s, when a naive compiler + asm would work perfectly.

Why though? At the end of the day, both are pretty C-like.

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#103
post #98

Earlier quoted context omitted.

It means that empty loops (loops with empty bodies) can be completely removed if the controlling expression has no side effects. > This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven. It means while(i) {} can be eliminated as if i were 0, because there are no side effects in the loop expression or the loop body, and what would be the point of the lo…

> If you really want to go to sleep, use pause() or similar. An infinite loop eats up CPU cycles Yes but an infinite loop + sleep is okay, right?

From compiler's POW call to sleep() is side-effect.

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#104

Earlier quoted context omitted.

Compiler writers have to use program transformation to do well on benchmarks. Developers who don't prioritize benchmarks probably don't use C, and if they do they really shouldn't, because sacrificing correctness for speed is the only thing C is good for these days.

Speed isn't the only reason to use C. I often use C not because it's fast, but because C is by far the simplest mainstream programming language. All these UB warts notwithstanding, no language's interface comes as close as C's to a basic, no-frills register machine.

You don't need speed, but you want to write in a language, which is closest to assembly? Hmm. Interesting view on what is simple.

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#106
post #70

Earlier quoted context omitted.

I would agree that strict-aliasing is a hurt point for a lot of C devs, which is unfortunate. I'd only suggest that in general, if the strict-aliasing rule is coming into play you're probably already doing some really shady to begin with. Like in this example, casting a `long ` to an `int ` is likely a bad way to go about things even without worrying about strict-aliasing. In a lot of ways, I'd say that problems with…

In the really simple cases (like accessing a float as a long, or similar), you're right. The problem is the interpretation that's been applied to aggregate types: struct a { int variant; }; struct b { int variant; long data; }; I have a pointer to a struct b - can I cast it to a pointer to struct a and still dereference 'variant'? It has the correct type and is guaranteed to live at the start of the struct in both ca…

> I have a pointer to a struct b - can I cast it to a pointer to struct a and still dereference 'variant'?

I think it is a bit of a gray area, but personally I've always held the opinion/understanding of no, that is invalid. The C standard does make one point fairly clear on strict-aliasing, which is the idea that strict-aliasing revolves on the idea that an object can only be considered to be one type of data (Or a `char` array, the only exception). Your example would be invalid for the reason that you can't treat an object of `struct b` as though it is a `struct a` - the fact that they share the same preamble doesn't change that. To be clear with what I'm saying: `struct b` can alias an `int`. `struct a` can also alias an `int`. But `struct b` can't alias a `struct a`, and because of this an int accessed through a `struct a` can't be accessed through a `struct b`.

That said, in general I find this to usually be a fixable problem, which also has (IMO) a cleaner implementation:

    struct head {
        int variant;
    };

    struct a {
        struct head h;
    };

    struct b {
        struct head h;
        long data;
    };
Now you can take a pointer to a `struct b` object and treat it like a pointer to a `struct head` object (Because it is a `struct head` object). You could do the same thing with objects of type `struct a`. So now you can cast both of them to `struct head` and examine the `variant` of both. Then later you could cast it back to the proper type.

This approach to aggregate types is heavily used in the Linux Kernel and other places (Including most of my own code). The `container_of` macro makes it even nicer to use (Though the legality of the `container_of` macro is an interesting debate...).

> The BSD socket API was built on exactly this kind of type punning.

Kinda. It's actually surprising how close it comes to skirting this issue (And it does skirt it), but I believe it's actually possible to use BSD sockets without ever violating the strict-aliasing rule (Though of course, there are ways of using it which would arguably violate the rule). In most cases for BSD sockets, strict-aliasing is more likely going to be broken in the kernel, not your code.

To note though, the strict-aliasing rule only applies to dereferencing pointers. You can cast pointers back and forth all day, you just have to ensure that when you're done you're treating it the object as you originally declared it. Thus, if you pass a `struct sockaddr_in` to `bind` and cast it to a `struct sockaddr`, the strict-aliasing rule isn't violated because you never dereferenced the casted pointer.

Going along with that, as long as you correctly declare your `struct sockaddr`s from the beginning you won't have any strict-aliasing woes. The only situation where this could technically be a problem is `accept` and `recvfrom`, since they are the only functions that gives a `struct sockaddr` back. But assuming you already know what address-family the socket is using, you can declare the correct `struct sockaddr` for that family from the start, cast it and pass it to `accept` or `recvfrom`, and then use it as your originally declared type without breaking strict-aliasing.

Of course, it's also worth keeping in mind that the BSD sockets interface came before C89. You definitely wouldn't design it the same way if you were to do it today.

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#107

Earlier quoted context omitted.

C programmers should be able to expect that "optimizations" will not transform program meaning. And because C is so low level, certain types of optimizations may be more difficult or impossible. If the pointer was explicitly set to NULL, the compiler can justifiably deduce the branch will not be taken but the deduction "if the programmer dereferenced the pointer it must not be NULL" is not based on a sound rule. In f…

> C programmers should be able to expect that "optimizations" will not transform program meaning. That's the official rule, but it's "program meaning as defined by the standard." It's not perfect, but nobody's come up with a better alternative. We get bugs because programmers expect some meaning that's not in the standard. But compilers are written according to the standard, not according to some folklore about what…

> But compilers are written according to the standard.

Written to the writers _interpretation_ of the standard. I bet money that every compiler written from a text standard hasn't followed said standard. It would be nice if a standard included code fragments used to show/test the validity of what is stated.

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#108
post #78

Earlier quoted context omitted.

In the large, no, they don't care about the 90-95% of the code base that's not performance critical. And these days, the stuff that is critical will be #ifdef and asm(...) stew. I can't tell you how many projects I have been on where disabling optimization made no measurable difference in performance. This being said, I cannot speak for game devs nor video device driver developers.

I have to say, I have never encountered a program where compiling without optimizations made no difference. If you have seen that, then I would agree that C was a very, very poor choice for that particular domain.

Business applications, as it was common in the 90's.

Witting them in C or VB made no difference for the guys sitting at the desk.

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#109
post #28

My 'favourite' bit of surprising (not undefined) behaviour I've seen recently in the C11 spec is around infinite loops, where void foo() { while (1) {} } will loop forever, but void foo(int i) { while (i) {} } is permitted to terminate...even if i is 1: > An iteration statement whose controlling expression is not a constant expression, that performs no input/output operations, does not access volatile objects, and pe…

[deleted]

Re: I Do Not Know C: Short quiz on undefined behavior (2015)

#110

Earlier quoted context omitted.

Speed isn't the only reason to use C. I often use C not because it's fast, but because C is by far the simplest mainstream programming language. All these UB warts notwithstanding, no language's interface comes as close as C's to a basic, no-frills register machine.

You don't need speed, but you want to write in a language, which is closest to assembly? Hmm. Interesting view on what is simple.

Maybe we mean different things by the word "simple". What language do you think is simpler than C?
Post reply on HN