Live data from Hacker News

Everything I wish I knew when learning C

tmewett.com

291–300 of 401 posts

Re: Everything I wish I knew when learning C

#291
post #74

> Everything I wish I knew when learning C By far my biggest regret is that the learning materials I was exposed to (web pages, textbooks, lectures, professors, etc.) did not mention or emphasize how insidious undefined behavior is. Two of the worst C and C++ debugging experiences I had followed this template: Some coworker asked me why their function was crashing, I edit their function and it sometimes crashes or do…

It's not insidious at all. C compiler offers you a deal: "Hey, my dear programmer, we are trying to make an efficient program here. Sadly, I am not sophisticated enough to deduct a lot of things but you can help me! Here are some of the rules: don't overflow integers, don't dereference null pointers, don't go outside of array bounds. You follow those and I will fulfill my part of making your code execute quickly". Th…

UB was insidious to me because I was not taught the rules (this was back in years 2005 to 2012; maybe it got more attention now), it seemed my coworkers didn't know the rules and they handed me codebases with lots of existing hidden UB, and UB blew up in my face in very nasty ways that cost me a lot of debugging time and anguish.

Also, the UB instances that blew up were already tested to work correctly... on some other platform (e.g. Windows vs. Linux) or on some other compiler version. There are many things in life and computing where when you make a mistake, you find out quickly. If you touch a hot pan, you get a burn and quickly pull away. But if you miswire an electrical connection, it could slowly come loose over a decade and start a fire behind the wall. Likewise, a wrong piece of code that seems to behave correctly at first would lull the author into a false sense of security. By the time a problem appears, the author could be gone, or she couldn't recall what line out of thousands written years ago would cause the issue.

Three dictionary definitions for insidious, which I think are all appropriate: 1) intended to entrap or beguile 2) stealthily treacherous or deceitful 3) operating or proceeding in an inconspicuous or seemingly harmless way but actually with grave effect.

I'm neutral now with respect to UB and compilers; I understand the pros and cons of doing things this way. My current stance is to know the rules clearly and always stay within their bounds, to write code that never triggers UB to the best of my knowledge. I know that testing compiled binaries produces good evidence of correct behavior but cannot prove the nonexistence of UB.

Re: Everything I wish I knew when learning C

#292
post #71

I'd recommend also reading Rob Pike's Notes on Programming in C, http://doc.cat-v.org/bell_labs/pikestyle

Quote which is interesting re Go lang..

> I eschew embedded capital letters in names; to my prose-oriented eyes, they are too awkward to read comfortably. They jangle like bad typography.

Re: Everything I wish I knew when learning C

#293

Earlier quoted context omitted.

I'm not sure that's a productive way to think about UB. The "weirdness" happens because the compiler is deducing things from false premises. For example, 1. Null pointers must never be dereferenced. 2. This pointer is dereferenced. 3. Therefore, it is not null. 4. If a pointer is provably non-null, the result of `if(p)` is true. 5. Therefore, the conditional can be removed. There are definitely situations where many…

The C and C++ (and D) compilers I wrote do not attempt to take advantage of UB. What you got with UB is what you expected to get - a seg fault with a null dereference, and wraparound 2's complement arithmetic on overflow. I suppose I think in terms of "what would a reasonable person expect to happen with this use of UB" and do that. This probably derives, again, from my experience designing flight critical aircraft p…

> What you got with UB is what you expected to get - a seg fault with a null dereference, and wraparound 2's complement arithmetic on overflow.

This is how it worked in the "old days" when I learned C. You accessed a null pointer, you got a SIGSEGV. You wrote a "+", then you got a machine add.

Re: Everything I wish I knew when learning C

#294

Earlier quoted context omitted.

> I really wish that int arr[5] adopted the semantics of struct { int arr[5]; } You and me both. In fact, D does this. `int arr[5]` can be passed as a value argument to a function, and returned as a value argument, just as if it was wrapped in a struct. It's sad that C (and C++) take every opportunity to instantly decay the array to a pointer, which I've dubbed "C's Biggest Mistake": https://www.digitalmars.com/artic…

That would be a nice little "gcc addition" to the C standard, honestly. To bad they spend most their time doing whatever it is they do.

Earlier versions of gcc actually used to support this in a very restricted context in C90 (or maybe gnu89) mode:

  struct foo { int a[10]; };
  struct foo f(void);
  int b[10];
  b = f().a;
In C90, you can't actually do anything with `f().a` because the conversion from array to pointer only happened to lvalues (`f().a` is not an lvalue), and assignment is not defined for array variables (though gcc allowed it). The meaning is changed in C90 so that non-lvalue arrays are also converted to pointers. gcc used to take this distinction into account, so the above program would compile in C90 mode but not in C99 mode. New versions of gcc seem to forbid array assignment in all cases.

I think this quirk also means that it's technically possible to pass actual arrays to variadic functions in C90, since there was nothing to forbid the passing (it worked in gcc at least, though in strict C90, you wouldn't be able to use the non-lvalue array). In C99 and above, a pointer will be passed instead.

Re: Everything I wish I knew when learning C

#295
post #288
post #278

Earlier quoted context omitted.

Miscompilations are rarer and less annoying in compilers that do not have the design behaviour of compiling certain source code inputs into bizarre nonsense that bears no particular relation to those inputs.

You realize these two statements are equivalent, right? > compiling certain source code inputs into bizarre nonsense > winning at compiled-binary-execution-speed benchmarks, giving fewer reasons for people to hand-write assembly code for the sake of speed (assembly code is much harder to read/write and not portable), reducing code size by eliminating unnecessary operations (especially -Os), reordering operations to f…

> compiling certain source code inputs into bizarre nonsense

> winning at compiled-binary-execution-speed benchmarks, giving fewer reasons for people to hand-write assembly code for the sake of speed (assembly code is much harder to read/write and not portable), reducing code size by eliminating unnecessary operations (especially -Os), reordering operations to fit CPU pipelines and instruction latencies and superscalar capabilities

Mainstream C compilers actually make special exceptions for the undefined behaviour that's seen in popular benchmarks so that they can continue to "win" at them. The whole exercise is a pox on the industry; maybe at some point in the past those benchmarks told us something useful, but they're doing more harm than good when people use them to pick a language for modern line-of-business software, which is written under approximately none of the same conditions or constraints.

> Don't shame other people building or using optimizing compilers.

The people who are contributing to security vulnerabilities that leak our personal information deserve shame.

Re: Everything I wish I knew when learning C

#296
post #81

Earlier quoted context omitted.

Interesting, I didn't fully realise that. That it's arbitrary is annoying, I clearly had tried to rationalise it to myself! Thanks for the comments, will get around to amending

Hi, great article. Regarding char, I'd remark that getchar() etc return int so it can return -1 for EOF or error. I'm pretty sure this implies int as a declaration is always signed, but tbh I'm not completely sure!

int, as all other integer types except char, is indeed signed by default.

Aside: signedness semantics of char is implementation-defined. However, the type char itself is always distinct from both signed char and unsigned char.

Re: Everything I wish I knew when learning C

#297
post #93

Earlier quoted context omitted.

From https://www.open-std.org/JTC1/sc22/wg14/www/docs/n2625.pdf : > The goal of the future language and library reservations is to alert C programmers of the potential for future standards to use a given identifier as a keyword, macro, or entity with external linkage so that WG14 can add features with less fear of conflict with identifiers in user’s code. However, the mechanism by which this is accomplished is overly…

So... instead of mandating implementations to warn about (re)defining a reserved identifier, they introduce another class of "not yet reserved indentifiers" and advise implementations to warn about defining such identifiers in the user code — even though it's completely legal, — until the moment the implementation itself actually uses/defines such an identifier at which point warning about such redefinition in the us…

The problem is that the traditional wording of C meant that any variable named 'top' was technically UB, because it begins with `to'.

In practical terms, what compilers will do is, if C2y adds a 'togoodness' function, they will add a warning to C89-C2x modes saying "this is now a library function in C2y," or maybe even have an extension to use the new thing in earlier modes. This is what they already do in large part; it's semantic wording to make this behavior allowable without resorting to the full unlimited power of UB.

Re: Everything I wish I knew when learning C

#298
post #200

Earlier quoted context omitted.

Perhaps I am missing something in the spec - but trying this in various compilers, it seems that you *can* assign structs holding arrays to one another, but you *cannot* assign arrays themselves. This compiles: struct BigStruct { int my_array[4]; }; int main() { struct BigStruct a; struct BigStruct b; b = a; } But this does not: int main() { int a[4]; int b[4]; b = a; } That seems like an arbitrary restriction to me.

In the first example a & b are variables, which can be assigned to each other. In the second a & b are pointers, but b is fixed, so you can not assign a value to it.

They’re not pointers. sizeof a == 4*sizeof(int), not sizeof(int*).

Re: Everything I wish I knew when learning C

#299
post #43

C is easier if you first learn assembler for an architecture or two :)

Everything is easier once you torture yourself with trying to learn Assembler :)

I dunno, I read Programming from the Ground Up, it's fine. World would be in better shape if that thorough commenting style was used more often.

Re: Everything I wish I knew when learning C

#300

I was born in '74 so the last generation to start with C and go to other, higher-level, languages like Python or JavaScript. Going in this direction was natural. I was amazed by all the magic the higher-level languages offered. Going the other direction is a bit more difficult apparently. "What do you mean it does not do that?". Interesting perspective indeed!

What was nice about C then was that, based on my study of CPUs at the time, you could pretty much get your head around what the CPU was doing. So you could learn the instructions (C) and the machine following them (the CPU).

When I got to modern CPUs it's so complex my eyes glazed over reading the explanation and I gave up trying to understand them.

Post reply on HN