Live data from Hacker News

Modern C [pdf]

icube-icps.unistra.fr

371–380 of 396 posts

Re: Modern C [pdf]

#371
post #61

Earlier quoted context omitted.

Usually declare variables one per line, so, type is still clear when: char* var1; char var2;

I tend to see typedef on a pointer type to address this. typedef char * pchar; pchar var1, var2; // Now they're both pointers I'm not really a fan, but it can address the problem.

The language is that of Redmont, which I shall not utter here.

Re: Modern C [pdf]

#372
post #362

Earlier quoted context omitted.

Which part of this paper is the part you're worried about?

The second half of the paper is mostly about challenges in using Rust. Some were related to the way the language handles data (i.e. challenges of sharing data among threads without using unsafe code), but there was also some that are related to the compiler and the standard libraries. The authors mentions allocation in particular (to be honest I did not really understand his problem). He also writes about the standar…

Yeah, that's why I asked! It's been a while since I read the paper, and skimming it, it wasn't clear which part you were asking about. Seems like mostly 3.2 and 3.3?

3.2.1 is about inheritance. That still may or may not be coming, exactly. It's just not a huge deal to more experienced Rust programmers, though we can and will be doing a better job of explaining alternative design strategies.

3.2.2 is about anonymous enums. The author is right that this is a little boilerplate-y today.

3.2.3 is about static data. The answer is the lazy_static crate, which is compatible with no_std; I use it in my own OS a lot. It works almost exactly how the author wants it to, though it doesn't literally use Option.

3.3.1 is about allocation, which is where you said you didn't really understand. So let's back up a minute: Rust's standard library is built in layers: the foundation is libcore, and then libstd is layered on top. Libcore is suitable for OS development, and includes nothing about allocation at all. Libstd includes heap allocation. This is referencing Box, which is in libstd. If there isn't sufficient memory, the the implementation of heap allocation in libstd will abort. That's still true today. However, this is one of the reasons that libstd isn't meant for this kind of development; it's meant for general application development. So, while the author's point is still sort of true today, I'd argue that it's going about it wrong, and it's also not a limitation of Rust generally.

3.3.2 goes on about this some more.

There's some stuff about Cargo in 2.8.3. Many OS dev projects augment Cargo with a Makefile, but depending on context, you can use only Cargo today. My (still toy) kernel does, for example. This section is extremely unclear as to what problems they faced, so it's hard to tell what's changed between then and now.

Other than that, I'm not totally clear on the "standard library being too large" and "emits a lot of code that depends on the standard libraries" bits you're talking about. I don't have time to fully re-read the entire paper right now, if you could give me specifics, happy to elaborate further.

A lot has changed since April 2015, including the release of Rust 1.0 :)

Re: Modern C [pdf]

#373
post #201

Pardon my noobness, but if I learned and became proficient in C and knew nothing else, would I have a marketable skill? Is it possible for C to be a standalone skill, where ones job could be 100% programming in C, or do you need a lot of auxiliary knowledge outside of that?

> would I have a marketable skill?

Yes. Systems programming and Embedded are your best and most visible playing fields, but many large, legacy applications were written in C and continue to be maintained.

> Is it possible for C to be a standalone skill,

No. As others have pointed out, the language + standard library is very spartan and will only take you so far. This will only get you an entry level position, and only in teams that are big enough to have some senior people with spare capacity for mentoring, and a stream of small, self contained tasks for you do while in training.

To be able to work independently you need to have at least some basic knowledge of the whole toolchain: Compiling (you need to know to heart the different steps that are taken by the compiler, and at least its 20% most common cmdline flags), Building (make), program analysis (lint, valgrind), debugging (gdb, or whatever comes with the compiler you are using), 3rd-Party-Libraries (pick 2-3 of: glib, pthreads, antlr, curses, openssl, etc), Standards (MISRA, POSIX - which is at least as much about the API to Unix-like OS as it's about the C language).

From there, there are more tools to help you, but those are typically OS dependent and are not exclusive to C.

Re: Modern C [pdf]

#374
post #270

Earlier quoted context omitted.

I think that you have the formulation backwards. You claim that people can just write better, and should attain perfection. > I don't think anyone can demonstrate that it is virtually impossible to write 100% safe C code. I think most people come at the other way. Most people are aware that they are fallible and wants tools to help with that. Most people strive for perfection and none will ever actually attain it. >…

So I see this argument as "should the tools catch these things?". I suppose that would make some people feel better. But the fact is, when you're in the seat, it's up to you to make sure you Do No Harm. But please be aware - generalizing all failures and integrating them into the tool suite is a pretty daunting task. Perhaps the economics of it make sense. But if you're stuck writing 'C', especially on legacy code ba…

That did sum up my argument well, same one extreme you are taking.

You don't need the compiler or exception to cover all your errors. If you know something would be too costly to integrate in these mechanisms then you are free to disregard it. I have written throwaway code that did gross things with pointers, memory and system specific resources. But if I want code to last and be maintainable I do my best to get the compiler to watch my back.

This also works well when interfacing with legacy C. If the new code can be written in composable and unit testable classes, then you can prove (only to the extent of the quality of your automated tests) that problems are in your code or in the legacy code as they arise. Then when you find problems in legacy code, try to break a piece out and replace it with another class, even a a big ugly one just so you can get some unit tests in there. Then you can break the big ugly class into smaller, cleaner, composable and well tested units.

Re: Modern C [pdf]

#375
post #271
post #270

Earlier quoted context omitted.

I think that you have the formulation backwards. You claim that people can just write better, and should attain perfection. > I don't think anyone can demonstrate that it is virtually impossible to write 100% safe C code. I think most people come at the other way. Most people are aware that they are fallible and wants tools to help with that. Most people strive for perfection and none will ever actually attain it. >…

> which then results in every caller ignoring the return value And a whole load of compiler warnings. Worse yet, people who ignore warnings might ignore them. > Now imagine the advances in error detection moving to languages that catch additional classes of errors. Languages don't catch errors, tools do. The C tooling has been and still is constantly improving.

In projects with centralized build scripts, like most projects, hopefully they have -Werror or its equivalent on by default. I was speaking about the case were a group has systematically ignored warnings and they are already beyond fixing. This is a depressingly common state for many shops. The best fix I have seen to enable as many warnings as possible and treat them as errors as early in the project lifecycle as possible. For whatever reason C++ shops are much more likely to do this than C shops in my experience.

If the compiler isn't the "language" enough for you, then please explain how to write a buffer overflow in Javascript?

Re: Modern C [pdf]

#376
post #299
post #278

Earlier quoted context omitted.

Comments like this perplex me. I am a full time C++ dev and I could just rewrite your comment woving the "++" to the other "C". Really like that I can create a class that stores all the knowledge of one concept internally and if I wrote that correctly I never need to look inside it again. Even better, if I document the contracts of using a class I can carefully optimize it and have broad performance effects with smal…

C++ has the issue that there are a lot of ways to hide magic, and a lot of hidden magic can blow up in unexpected ways if it interacts badly with other parts of the language that you do not understand. The result is that you really need to stick to a subset of the language that has been chosen to work well together. Safely adding to that chosen subset is challenging. And it just takes one developer to create a major…

Every language can hide stuff. Classes, templates and operator overloads are just pretty wrappers for functions with better designed conventions for the common cases. Anyone can write "hidden magic" in any language with anything like these abstractions.

It is particularly easy to make this hidden magic in C. For example, there is no way to express who has ownership of a pointer or when a function expects a pointer or an array. I have seen plenty of C libraries that document things like this, but for each that does there are 3 that don't. For each one does document things like that, they do it differently with different conventions but for the same reasons, but I cannot even use common idioms to be safe I must understand every part of each library I call. It is much more clear what is going own in C++ when a function accepts or returns a std::unique_ptr and I cannot screw it up without trying hard.

What seems more important to me is exposing the relevant parts of the software when needed. If I care about the business logic (Even if its nots a business app... HP and Mana can be considered the business logic of a game) it can be hard to tease that out when lots of "hidden magic" is shoved in my face. But when I need to handle a new file format that the business logic requires indirectly I don't want to mess with the business logic. Having more tools to cleanly express this helps. So have a type that handles this IO while some other type handles business logic is indeed changing magic into "hidden magic", but it is also enforcing separation of responsibilities. Something much harder to do when your only real tool of abstraction if functions.

The only people I have met in real life that stick to anything like the "hidden magic" argument are the same people who advocate for single large functions. These people like their function on the order of hundreds or thousands of lines so they can "see everything". You aren't doing that are you?

Re: Modern C [pdf]

#377

Earlier quoted context omitted.

There is also the issue of undefined and implementation defined behavior. When developing on one platform for an extended period of time, it is human nature to forget which features are implementation defined as you use them day after day and then have unexpected errors/flaws when porting.

Undefined behaviour actually isn't the monster that most C language lawyers want you to believe it is. With tools like valgrind, address sanitizers and modern debugging toolchains, most of these issues can be caught. Compilers are also mature enough to issue warnings about the use of uninitialized variables, missing return statements or mismatched printf specifiers. Heck, Clang maybe has more than 250 -W options.

> With tools like valgrind, address sanitizers and modern debugging toolchains, most of these issues can be caught.

Most of these tools require support from an operating system. This is not the case when you do kernel programming. For some reason even existing tools are not popular among kernel programmers [0].

IMO, there are bugs that can be caught well a compile time without my effort, so why should I waste time on catching them at runtime?

I would better make love to compiler instead of having sex with debugger.

[0] http://lwn.net/2000/0914/a/lt-debugger.php3

Re: Modern C [pdf]

#378
post #376
post #299

Earlier quoted context omitted.

C++ has the issue that there are a lot of ways to hide magic, and a lot of hidden magic can blow up in unexpected ways if it interacts badly with other parts of the language that you do not understand. The result is that you really need to stick to a subset of the language that has been chosen to work well together. Safely adding to that chosen subset is challenging. And it just takes one developer to create a major…

Every language can hide stuff. Classes, templates and operator overloads are just pretty wrappers for functions with better designed conventions for the common cases. Anyone can write "hidden magic" in any language with anything like these abstractions. It is particularly easy to make this hidden magic in C. For example, there is no way to express who has ownership of a pointer or when a function expects a pointer or…

The problem is not so much hiding stuff. It is being surprised by the interactions between features. The "can't get there from here" like trying to printf to an iostream. If you're not careful, simply scanning through a large file is several times slower than other languages. Template magic makes seemingly reasonable type names blow up into insanely hard ones to figure out. There are non-obvious patterns that you have to know such as RAII.

And good luck if you want to make things portable! I remember at Google being asked how I checked in unit tests that broke integration tests. Turns out that GCC and Clang disagreed on whether a friend of a subclass can see protected elements in the parent class. The local language lawyer decided that gcc was right, sent off a bug report to Clang, and I had to make my little unit test class a friend of the parent class as well. Maybe I was unlucky, but why does this sort of thing not happen to me in other languages?

In other languages I am consistently able to read up on the language syntax and features, implement things within my knowledge, catch my bugs with unit tests, and have a basically working system. I've had this experience with C, Lua, Perl, Ruby, Python, PL/SQL, Java, JavaScript, etc.

But C++ finds ways to astound and surprise me. Perhaps if I was a full-time C++ developer I'd learn all of the corners and would simply be productive. But the last time that I wrote a non-trivial C++ program, there wound up being multiple things which worked right in unit tests but not the full program until I ran Valgrind and followed suggestions that I would have had no way of figuring out on my own.

Yes, I'm aware of how easy it is for third party libraries to be bad. From dangling pointers in C to monkey patching in Ruby there is a lot of crazy stuff that can be screwed up by third party developers. But C++ is the only language where I had trouble not screwing myself up.

Re: Modern C [pdf]

#379

Earlier quoted context omitted.

Well, function prototypes in the first ANSI standard were a fine addition, if you ask me. After that...

This is where it all started. In general, I would agree with you, but the immediate loss of the original elegance and simplicity of the design is obvious. (Also, I believe it was a deliberate design decision to only require that the return type be specified - if it is not 'int' - in a declaration of a function. Besides having resulted in easy-to-understand semantics and an uncluttered syntax, this opened a direct way…

Yes, the fact that calling conventions changed with prototypes in a way that was incompatible with and could not be duplicated by the existing calling conventions was a mistake.

I wrote a little about a very similar case with Objective-C and the default 'id' argument and return type here: http://blog.metaobject.com/2014/03/cargo-cult-typing-or-obje...

Re: Modern C [pdf]

#380
post #351
post #335

Earlier quoted context omitted.

C has a char* type, which we call a string, but it is also the type of a pointer to a single char, which is not a string at all, and also something perfectly usable. "Ends with nul" is barely a part of C, it's more like a programmer's agreement. The language doesn't enforce it, require it, or check it. All it does is insert nul characters in literals, which is hardly enough to make a string type. Thus if you have a t…

From the signature, I would say it expects a NUL-terminated sequence of characters (a C-string) and it would modify it in-place to upper case each character. C already has a standard C function: extern int toupper(int); (via #include ) that will upper case a single character. If, on the other hand, I saw: extern char *to_upper(const char *); I would expect that to_upper() returns a new string (freeable via a call to…

The signature doesn't tell you that. If my API said

    int frobnicate(char*)
and you make that kind of assumption, then your code may or may not work, depending on what the function does internally. You simply do not know whether I am operating on null-terminated char sequences or a single char.

>Um ... how do you "happen" to have a pointer-to-char?

    char* text = "some text";
    char* c = text[2]
There you go.

>And unknowingly call to_upper()?

Who said anything about unknowingly calling a function? It's "toupper", not "string_to_upper" or "char_to_upper". The function signature simply doesn't tell you what the function requires of its input.

PS: char* is also a pointer-to-byte in C.

Post reply on HN