Live data from Hacker News

Modern C [pdf]

icube-icps.unistra.fr

381–390 of 396 posts

Re: Modern C [pdf]

#381
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…

This is all true. So don't do that.

Don't worry about me, I never make any mistakes. I'm a true C programmer: I believe that "implement a good string type" is an unsolved problem and that the last 50 years never happened.

Re: Modern C [pdf]

#382

Hi, is there a epub version of this book? The pdf format is painful to read.

Seconded. If it is not feasible I would appreciate a narrow PDF (like A5) with minimal margins which should be good enough for reading on a Kindle.

Re: Modern C [pdf]

#383
post #378
post #376

Earlier quoted context omitted.

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 hav…

Phrased this way your concerns seem much more valid.

As for C++ file IO, I think it sucks too that something as idiomatic a iostream iterators are pretty much garbage. I hope they are removed in STL2 and they keep the good parts of iostreams and ditch the bad. With tellg and seekg you get the same or better performance as the c lib functions unless you need to care about visual studio performance... but if you are using that compiler you never actually compared enough about performance to benchmark.

I feel I must point at that C features shouldn't be expected to interact with C++ features. Printf does what it does, it was not designed to live with objects and types. It is a holdover from the C days. It is totally unaware of non-trivial types and does really gross things when you give it the wrong type. Using some other function to write to something that can be streamed or writing and operatorAs for finding implementation specific bugs, you claim not to have found them in other languages, then included Javascript on a list of things you have used. Javascript is the poster child for implemtation specific problems, to the point where there are several sites that put real effort into describing differences between different implementations. This whole listing seems odd. These languages either have 1 implementation (Lua) so cannot have differences or are so under-specified that of course the implementations have huge differences (Ruby, Python, Sql). Clearly I have had more problems with all these then you I find all languages terrible at this point, I just a few to be less terrible.

I think I may know why you are shooting yourself with C++, as you put it. If you find RAII non-obvious after you have worked with it then there is definitely a problem with how you are approaching something about C++.

RAII, or deterministic deconstruction in general, is probably the single strongest thing the C++ language brings to the table. With RAII you can implement your own memory safety model via any kind of shared pointer you can dream up. With RAII you can prevent race conditions by creating exception safe mutex guards. Write your own transaction handler by putting the roll-back code in the deconstructor. With RAII you can clean up any kind of resource in a deterministic and safe way that few other languages offer.

I had to some work with automated UI testing recently (a complex application and framework with C++, Java, Ruby and Python). My application leaked resources in a very gross way because we had to pass handles to our resource back out to users writing scripts that could manipulate them. This leaky resource was whole web browsers.

For a combination of technical and business reasons the only suitable tool for creating browser instances. If I could have relied on Java's finalizers to be called I could easily have close them there. We found several situations where they clearly failed and much documentation about the reason they failed (and apparently how the JVM could be fixed if the standard authors were so compelled). After a couple of weeks of research and failing to be able to explain the various segments of the Java community the "using" keyword was inadequate for this usage pattern, the smallest hack we could come up with was a silly watchdog timer that checked the processes on the machine and knew when the web browser manipulation API was used. This was almost a 1000 lines of code to get right to buy nothing but resource safety in the face of exceptions. It would have been 4 lines of C++ and two of those would have been curly brackets.

Of course I am biased, I pick a story from my experience to suit my argument as you have done yours. I am still not sure how one hurts themselves more with C++ than particularly and if you have access to C++11, C++14 or C++17 it seems a fair bit safer than Java or Ruby because of the precise guarantees and strong tools for safety the language lacked before. Still can't keep up with With Rust or Haskell in the safety department though.

Re: Modern C [pdf]

#384
post #265
post #237

Earlier quoted context omitted.

It's not just that HN does a lot of webdev. It's that even in its element as a "systems language" it's virtually impossible to write 100% safe C/C++ code and guarantee that it will remain safe into the future, even for experts who are making every effort to do it right. There are just too many gotchas with "undefined behavior" and too many clever compilers out there waiting for you to make a mistake. One only needs t…

I don't think anyone can demonstrate that it is virtually impossible to write 100% safe C code. Sure, you can always find people who don't know how to write a proper safety check. That doesn't mean nobody knows. You can always find people who ignore or don't know about best practices, but that doesn't mean everyone's like them. And you can find people who write goto fail; and ignore the warnings about unreachable cod…

> That doesn't mean nobody knows.

Yep. Have a look at the code coming from the OpenBSD crowd. Those folks really know how to wield C. It involves, first and foremost, writing readable and straightforward code, in an attempt to make any bugs obvious. The OpenBSD folks also insist on code review, which also helps.

And wrt tooling: C has some of the best tooling around of any language. GCC, Clang, and Visual C++ can all do some pretty decent static analysis, and then there are tools like lint and Frama-C, and tools like valgrind. Coverity also offers free static analysis for open-source projects. Make use of all the tools available to you. Testing is also important. Shoot for 100% code coverage (see SQLite3, for example, which has a massive test suite).

As you say, one of the requirements is to pay attention to warnings and fix them. In compiler parlance, "error" means "I can't compile this code" while "warning" means "I can compile it, but it's going to misbehave at runtime".

And here's something about undefined behavior: it's possible to know which behavior is undefined and to avoid it! Not every C program is riddled with undefined behavior.

Re: Modern C [pdf]

#385
post #9

It's been a decade or more since I've worked in C (and have never been a heavy C coder). Is "modern C" really a thing? I mean, is there some subset of C that is safer than what I think of when I think of C? I know about stuff like reference counting techniques, rather than manual memory management, for example, and that goes miles towards safer coding. But, even so, the variety of ways you can shoot yourself in the f…

> It's been a decade or more since I've worked in C (and have never been a heavy C coder). Is "modern C" really a thing? Well, I've done C off-and-on, sometimes heavily, since the days of VT-100s and DEC-Writers. Modern C has always been a thing since the 1970's. Its just that the definition of "modern" keeps changing :) Yes, things are easier. It is possible to use the compiler features to write code that can be com…

> "Life is too short for C++"

I've said for many years "Life is too long to write C++ for a living." And I haven't written C++ for many years.

Re: Modern C [pdf]

#386
Would any C-lovers recommend this book to people that already know programming (but not sys programming) wanting to learn C?

I personally know Java, (lil bit) Elixir, and Python.

EDIT: I'll also be reading K&R along side it.

Re: Modern C [pdf]

#387
post #380
post #351

Earlier quoted context omitted.

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 unknowin…

Your response to me shows you don't program in C all that much. I ran your code example through a C compiler and got:

    a.c:2: warning: initialization makes pointer from integer without a cast
    a.c:2: error: initializer element is not constant
What you really want is:

    char * text = "some text";
    char * c    = &text[2];
which still doesn't prove your point because c is still pointing to a NUL-terminated string.

If fronnicate() really takes a single character, I might ask why the function requires a pointer to char for a single character instead of:

    int frobnicate(char);
but if you are going to really argue that point, so be it. Discard the fact that in idiomatic C, a char * is generally considered a NUL-terminated string (and here I'm talking ANSI C and not pre-ANSI C where char * was used where void * is used today).

You are also shifting the argument, because in your original comment I replied to, the function you gave was to_upper(). toupper() is an existing C function.

P.S. char * is a pointer-to-character, not a "pointer-to-byte", pedantically speaking. All the C standard says is that a 'char' is, at minimum, 8 bits in size. It can be larger. Yes, there are current systems that this is true.

Re: Modern C [pdf]

#388
post #362

Earlier quoted context omitted.

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 e…

Thanks for the answers!

> Other than that, I'm not totally clear on the "standard library being too large"

IIRC their Rust kernel hit some size barrier (for the bootloader?). The author tried to build a smaller standard lib to overcome this, which was a bit of work.

Re: Modern C [pdf]

#389
post #383
post #378

Earlier quoted context omitted.

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 hav…

Phrased this way your concerns seem much more valid. As for C++ file IO, I think it sucks too that something as idiomatic a iostream iterators are pretty much garbage. I hope they are removed in STL2 and they keep the good parts of iostreams and ditch the bad. With tellg and seekg you get the same or better performance as the c lib functions unless you need to care about visual studio performance... but if you are us…

It is not that RAII is non-obvious after you've worked with it. It is that you can read through how the language works somewhere like http://www.cplusplus.com/doc/tutorial/, start producing software, and not realize that you have to do RAII. You can even, as Google did, have experienced and competent people write a large and well-structured program and then only belatedly realize that you can't use certain features because they didn't structure the program right.

There is a lot of that in C++. If you get everything right, then wonderful. If you don't, then that is a problem.

On implementation bugs, I have found implementation bugs in lots of languages. But not generally as things that I stumble over while proceeding with what seems to me like it should be the obvious thing to try.

With C++ it isn't like that. I gave you an example where there is a disagreement between compilers. But, for example, what happens if I supply an ordering method that isn't consistent? In other languages I get stuff sorted slightly inconsistently. In C++ I get a core dump. Good luck figuring out why.

On the complaint that you have about Java, that falls in the category of things that I expect to have to deal with. Part of my mental model for a language has to be the memory management rules. C++ lets you set up whatever you want. Perl does deterministic destructors but therefore can't collect circular data structures without help. Java has true garbage collection so it collects circular data structures, but it can't guarantee that they are collected in a timely way. JavaScript does true garbage collection now, but back in the IE 4.0/5.0 days they separately collected garbage for native objects and JavaScript objects with the result that garbage never got collected if there was a cycle between native and JavaScript objects.

This is one of the basic facts that I know that I have to understand about any language I work with. It is like pass by value vs pass by reference, or the scoping rules. I immediately look for it, understand it, and then am not surprised at the consequences. I see other people use the language for a while without trying to understand that. I understand their pain. But I'm not caught by surprise.

However C++ keeps finding new ways to surprise me. In the past I reserved it for cases where I need to implement an algorithm and squeeze and order or two new magnitudes of precise memory layout and performance beyond what is available in scripting languages. I've resolved that the next time I need that, I'll try a new language. My past experiences with C++ haven't been worth it.

Re: Modern C [pdf]

#390
post #388

Earlier quoted context omitted.

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 e…

Thanks for the answers! > Other than that, I'm not totally clear on the "standard library being too large" IIRC their Rust kernel hit some size barrier (for the bootloader?). The author tried to build a smaller standard lib to overcome this, which was a bit of work.

No problem!

I didn't see that from skimming; if it was bootloaders, well, they have a max size of 512 bytes (on x86) so virtually all bootloaders load in stages, with a tiny bootloader that loads the "real" bootloader.

Post reply on HN