Live data from Hacker News

Everything I wish I knew when learning C

tmewett.com

171–180 of 401 posts

Re: Everything I wish I knew when learning C

#171
post #125

Earlier quoted context omitted.

> how insidious undefined behavior is. Indeed. UB in C doesn't mean "and then the program goes off the rails", it means that the entire program execution was meaningless, and no part of the toolchain is obligated to give any guarantees whatsoever if the program is ever executed , from the very first instruction. A UB-having program could time-travel back to the start of the universe, delete it, and replace the entire…

> Indeed. UB in C doesn't mean "and then the program goes off the rails", it means that the entire program execution was meaningless, and no part of the toolchain is obligated to give any guarantees whatsoever if the program is ever executed , from the very first instruction. I don't think this is exactly accurate: a program can result in UB given some input, but not result in UB given some other input. The time trav…

They might be referring to eg. the `_Nonnull` annotation being added to memset. The result is that this:

   if (ptr == null) {
      set_some_flag = true;
   } else {
      set_some_flag = false;
   }
   memset(ptr, 0, size);
Will never see `set_some_flag == true`, as the memset call guarantees that ptr is not null, otherwise it's UB, and therefore the earlier `if` statement is always false and the optimizer will remove it.

Now the bug here is changing the definition of memset to match its documentation a solid, what, 20? 30? years after it was first defined, especially when that "null isn't allowed" isn't useful behavior. After all, every memset ever implemented already totally handles null w/ size = 0 without any issue. And it was indeed rather quickly reverted as a change. But that really broke people's minds around UB propagation with modern optimizing passes.

Re: Everything I wish I knew when learning C

#172
post #92

Earlier quoted context omitted.

I would go one step farther: The documentation will say it is undefined behavior but the compiler doesn't have to. Here's an example from the man page for sprintf sprintf(buf, "%s some further text", buf); If you miss that section of the manual, your code may work, leading you to think the behavior is defined. Then you will have interesting arguments with other programmers about what exactly is undefined behavior, e.…

I'll tell you what happens when someone writes: sprintf(buf, "%d %d", f(i), i++); They get told to rewrite it.

Good point, actually. Many cases of undefined behavior are clearly visible to an experienced C programmer when they review someone else’s code.

Re: Everything I wish I knew when learning C

#173
post #116

Earlier quoted context omitted.

Isn’t all UB a result of logic errors? Writing beyond the end of allocated memory (due to incorrect bounds calculation ) is an example of undefined behaviour

No, even type-punning properly allocated memory (e.g. using memory to reinterpret the bits of a floating point number as an integer) through pointers is UB because compilers want to use types for alias analysis[1]. In order to do that "properly" you are supposed to use a union. In C++ you are supposed to use the reinterpret_cast operator. [1] Which IMO goes back to C's original confusion of mixing up machine-level co…

I believe using reinterpret_cast to reinterpret a float as an int is undefined behavior, because I don't believe that follows the type aliasing rules [1]. However, you could reinterpret a pointer to a float as a pointer to char, unsigned char, or std::byte and examine it that way.

As far as I'm aware, it's safe to use std::memcpy for this, and I believe compilers recognize the idiom (and will not actually emit code to perform a useless copy).

[1] https://en.cppreference.com/w/cpp/language/reinterpret_cast

Re: Everything I wish I knew when learning C

#175
post #101
post #92

Earlier quoted context omitted.

I would go one step farther: The documentation will say it is undefined behavior but the compiler doesn't have to. Here's an example from the man page for sprintf sprintf(buf, "%s some further text", buf); If you miss that section of the manual, your code may work, leading you to think the behavior is defined. Then you will have interesting arguments with other programmers about what exactly is undefined behavior, e.…

I remember reading a blog post a couple of years back on undefined behavior from the perspective of someone building a compiler. The way the standard defines undefined behavior (pun not intended), a compiler writer can basically assume undefined behavior never occurs and stay compliant with the standard. This offers the door to some optimizations, but also allows compiler writers to reduce the complexity in the compi…

Yeah a decent chunk of UB is about reducing the burden on the compiler. Null derefs being an obvious such example. If it was defined behavior, the compiler would be endlessly adding & later attempting to optimize-away null checks. Which isn't something anyone actually wants when reaching for C/C++.

Similarly with C/C++ it's not actually possible for the compiler to ensure you don't access a pointer past the end of the array - the array size often isn't "known" in a way the compiler can understand.

Re: Everything I wish I knew when learning C

#176
post #65

I would add to this: 1. Visual Studio debugger is really, really good and you should use it to step through your program (or equivalent IDE based workflow). 2. Learn to compile your code with memory safeguards and use the tooling around them. Specific depends on the platform. On POSIX address sanitizer is good I hear. On Windows you can use the debug CRT and gflags to instrument your binary really well.

People should be using IDEs with modern debuggers anyways. Being able to step through your code one line at time and instantly see the values of all your variables is going to be FAR more effective than adding a bunch of print statements, no matter what language you're using. It always blows my mind to hear about how many engineers don't know how to use their IDE's debugger and don't know what a breakpoint is.

Perhaps there is not enough training available on these tools. Raw terminal GDB also is not very user friendly so if someone has experience from that it might be a disencouragement.

Re: Everything I wish I knew when learning C

#177

I like it, but the array details are a little bit off. An actual array does have a known size, that's why when given a real array `sizeof` can give the size of the array itself rather than the size of a pointer. There's no particular reason why C doesn't allow you to assign one array to another of the same length, it's largely just an arbitrary restriction. As you noted, it already has to be able to do this when assi…

> There's no particular reason why C doesn't allow you to assign one array to another of the same length, it's largely just an arbitrary restriction.

IIRC C has an informal guarantee that no primitive syntax will ever cause the CPU to do more than O(1) work at runtime. Assignment is always O(1), and therefore assignment is limited to scalars. If you need assignment that might do O(N) work, you need to call a stdlib function (memcpy/memmove) instead. If you need an allocation that might do O(N) work, you either need a function (malloc) or you need to do your allocation not-at-runtime, by structuring the data in the program's [writable] data segment, such that it gets "allocated" at exec(2) time.

This is really one of the biggest formal changes between C and C++ — C++ assignment, and keywords `new` and `delete`, can both do O(N) work.

(Before anyone asks: a declaration `int foo[5];` in your code doesn't do O(N) work — it just moves the stack pointer, which is O(1).)

Re: Everything I wish I knew when learning C

#178
post #98

When I first learned C - which also was my first contact with programming at all - I did not understand how pointers work, and the book I was using was not helpful at all in this department. I only "got" pointers like three or four years later, fortunately programming was still a hobby at that point. Funnily when I felt confident enough to tell other people about this, several immediate started laughing and told me w…

Pointers are by far the most insidious thing about C. The problem is that nobody who groks pointers can understand why they had trouble understanding them in the first place. Once you understand, it seems so obvious that you cannot imagine not understanding what a pointer is, but at the beginning, trying to figure out why the compiler won't let you assign a pointer to an array, like `char str[256]; str = "asdf"`, is…

What is so difficult about the concept of a memory address? Is it the C syntax? Asking because I personally have never struggled with this.

Re: Everything I wish I knew when learning C

#179

Earlier quoted context omitted.

Isn’t all UB a result of logic errors? Writing beyond the end of allocated memory (due to incorrect bounds calculation ) is an example of undefined behaviour

That's like saying all bugs are undefined behavior. C lets you write to your own stack, so if you corrupt the stack due to an application error (e.g. bounds check), then that's just a bug because you were executing fully-defined behavior. Examples of undefined behavior would be things like dividing by 0 where the result of that operation can differ across platforms because the specific behavior wasn't defined in the…

Writing past the end of an array is defined as UB.

Not all bugs are UB, you can have logic errors of course. But stack corruption is I believe always triggered by UB.

Re: Everything I wish I knew when learning C

#180

" ... is often called the stack." " integers are cursed" These statements and a few others made me uncomfortable. They imply, to me, that the author has too little knowledge of computer internals to be programming in C. C does a wonderful job of looking like a high level language but it was designed to do low level stuff. There is an implicit assumption, to my mind, that the user has a deeper understanding of what is…

> " ... is often called the stack." What's wrong about the stack part? It seemed a little odd (and unnecessarily abstract) that the author said "automatic storage" instead of just stack is there a situation where there's automatic storage but no stack?

The C standard does not use the term "stack," it uses the term "automatic storage duration."

The idea is to separate implementation from semantics.

Post reply on HN