Unfortunately, C does get a lot of hate on HN. I suspect it has to do with this site's demographics. Many (not all) of the HN clan seem to be oriented towards / mostly familiar with web based technologies. I suspect that for many who have tried, going from a web dev environment to a C oriented dev environment feels like a robust shock to the system.
I'd also be willing to bet that there's an age bias at play here; C has been around, like, forever. It is certainly not the new hotness. Most (not all) people that I know who enjoy it and are proficient at it, are 40 or older. Much of the web based dev crowd that hang around HN seem to be in their 20s, and as it is a time honored tradition to poo-poo the ideas / methods / tech of the older generation(s), it's not surprising that C doesn't get a lot of love.
Yes, I realize I'm painting with broad strokes here. It'd be interesting to see a survey or three that correlates age ranges and tech used on a day-to-day basis to see if these assumptions or legit. (Anyone got any survey data up their sleeve they'd be willing to share?)
Me personally - I love it all. C, C++, Java, Python, Javascript, Rust, Haskell, Scheme, etc. Making computers do things for you, and for other people, by writing detailed instructions is quite possibly one of the funnest things in the world. Double bonus for getting paid to do it!
One only needs to look at something like the OpenSSL library to see the problem. You really need to hammer the hell out of C code with something like AFL to get at a reasonable majority of bugs - and you could hammer out every last bug one day and then the next day a compiler starts optimizing away your safety checks. This isn't a theoretical problem, this actually happens. Code rot is a very real problem in C++, to a far more massive extent than any other language.
http://blog.llvm.org/2011/05/what-every-c-programmer-should-...
http://www.kb.cert.org/vuls/id/162289
Personal opinion here, but with few exceptions C/C++ are inappropriate languages for starting new development at this point. I realize the tooling is not there yet but I would rather see something like Rust used in almost all performance-sensitive applications where C/C++ are currently used. Unless you can guarantee that you are operating in a trusted environment and will only ever operate on trusted data, C/C++ is just not the right language for the job.
Yes, it's fast, but at what cost? I would gladly give up a massive fraction of my performance for better security and portability - and that's why I program Java. Not that Java is perfect either, but at least I can be certain that the sands aren't shifting out underneath my programs.
I would actually say that porting the Linux kernel to Rust would be very high on my wish-list at this point. I am well aware of just how enormous that task would be and I might as well wish for a pony too, but it gives me heartburn to think of just how much C code is sitting there operating in the most untrusted of environments on the most untrusted of data. I have every faith in the kernel guys to do it right, but the reality is there is a lot of attack surface there and it's really easy to make a mistake in C/C++. It may not even be a mistake today, only when the compiler gets a little more clever.
While I agree with the sentiment, a problem with Java is that you're dependent on a runtime environment with a fairly consistent history of vulnerabilities, right? [0][1]
> Personal opinion here, but with few exceptions C/C++ are inappropriate languages for starting new development at this point.
Maybe, but now there's SaferCPlusPlus [2]. At least it may be a practical option for improving memory safety in many existing code bases.
[0] http://www.cvedetails.com/product/19117/Oracle-JRE.html?vend...
[1] http://www.cvedetails.com/product/1526/SUN-JRE.html?vendor_i...
[2] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus
You simply can't just write 'C' without making sure all the details that are necessary to run safely are in scope at all times.
While I agree - the OpenSSL cases certainly show the weakness of the language, there's just no way I'm gonna hang all that on 'C'. Writing protocols and protocol drivers is a fairly tedious sort of skill to attain. We inevitably descend into a counterfactual ... "fantasy" ( sorry; don't mean anything insulting by that - besides I do it too - it is just the nature of counterfactuals ) in which 'C' ends up the villain, when it was a much richer set of failures in play.
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.
> 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.
> I don't think anyone can demonstrate that it is virtually impossible to discover errors safely in C code.
There is a huge difference simply moving from C to C++ with exceptions. The type system in C++ can detect several classes of errors at compile time and prevent then grom going into the results.
Then for runtime problems if an underlying functions throws, it cannot simply be ignored. Any programmer can miss a single statement, or worse refactor a function with a void return to one that returns and error code (which then results in every caller ignoring the return value). However, it takes a special kind of malice to use something like carelessly catch(...) in C++ to disregard exceptions so that runtime errors are avoided. C++ with exceptions has more sane defaults because it fails fast and the failing itself doesn't need tests until it starts doing something meaningful.
Now imagine the advances in error detection moving to languages that catch additional classes of errors.
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.
https://www.bell-labs.com/usr/dmr/www/chist.html
I am yet to see it being use in enterprise C code.
If the compiler isn't the "language" enough for you, then please explain how to write a buffer overflow in Javascript?
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 bases with legacy tools, you're stuck, and there's only the one thing to do...
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.
1. The team is average or below since they're affordable or the work kind of sucks. This often happens in practice even with smart coders because the deadlines force them to move too fast with too little QA. Product might still have high impact, though, esp if it's widely-used product or service. The language itself preventing common problems is helpful here.
2. It's a FOSS project made by people that want to get stuff done without learning tons of rules for working around C's issues or stopping every common operation to prevent language itself from killing their project. I'd say vast majority of projects don't need whatever absolute advantages like max performance that C has over safer languages. Again, the language could be helpful.
3. Either of the above given the effects of time where new contributions come in that work against a codebase that fewer and fewer people understand due to organic growth. The language itself can be helpful with a combo of type-safety, programming in the large support, modules, etc. Better support for safer modifications of stuff you barely understand. Rarely a problem for Ada and Eiffel people if the team was even half-competent because the compiler simply demands it.
There's embedded people that can do one-off or steady projects however they like with enough time and tooling to get it right. ArkyBeagle appears to be in a category like that if my broken memory isn't fooling me. Then, there's vast majority of programmers either in the corporate crunch, scratching an itch barely caring, or fixing something they barely understand. Human nature will push defects in from all these directions. The tooling, if designed with human nature in mind, can prevent a lot of them automatically and aid efforts to catch the rest.
Hence, my opposing C language in favor of safer-by-default system languages. Especially those that avoid tedium of constantly watching out for dangers of most-common operations. Gotta work with human nature rather than against it. A hard lesson I learned after years of failed evangelism of high-assurance INFOSEC. Now, I exclusively look for ways to embed it seemlessly into stuff with other benefits listed. Much better responses on that. :)
http://arstechnica.com/security/2016/09/linux-kernel-securit...
As someone who went the "other direction" (Java -> Ruby -> Javascript) I can say that a lot of it has to do with the accessibility of the ecosystem rather than the language itself. This could absolutely just be my filter bubble, but I've noticed that the communities surrounding Ruby, Python, and Javascript seem to go above and beyond the call of duty when it comes to making libraries easy to use, documenting those libraries, building and refining the tools, and so on.
I know there are good tools out there for C development. I know there are good learning materials. I know there are communities out there dedicated to writing good C code (Shout-out to /r/c_programming on Reddit. Love those folks.) But I can't sort out the signal from the noise, because there isn't a lot of discussion about C programming happening in the online spaces I'm familiar with. As a counterexample, there was a _fantastic_ article on here the other day about "writing your own syscall" in Linux. Yes, it contains a lot of hand-holding and overexplanation, but that's useful for me because I haven't built up the mental model to parse a more terse explanation.
In fact, I think this is how having "the new hotness" change every couple years has been helpful _in some respects_- there's an incentive for lots of people to write blog posts, tutorials, and articles about how to properly use the latest and greatest tech, there's active development going on as people forward-port functionality (and therefore plenty of opportunity for devs to make meaningful contributions and have meaningful discussion about "how to write code using this language/library/framework"). For a short period, both the "old hands" and the newbies are in the same boat, and this is unbelievably useful for training up the next generation of developers.
> Me personally - I love it all. C, C++, Java, Python, Javascript, Rust, Haskell, Scheme, etc. Making computers do things for you, and for other people, by writing detailed instructions is quite possibly one of the funnest things in the world. Double bonus for getting paid to do it!
Same here, friend. :) For what it's worth, I wish there were more of this attitude floating around the Internet.
Personally I'm in my mid-20s and quite enjoy working in C. And for things like bit manipulation it's much easier than in higher level languages. I suspect at some point even the smallest MCUs will be able to run Rust or Go, but until that happens there is still a place for C/C++. Haters can hate but that won't change the fact that C is still the most widely supported language for embedded platforms (and Linux, the other elephant in the room).
It's been done at least once before for ideological reasons (and in C none-the-less) by the FSF. It should be even easier to give it a go in modern languages. I bet you can even get funding if you can write a compelling case that the wheel is actually broken!!!
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.
Most people who complain about the dangers of C probably have used it in an unprofessional setting without any additional tooling. It's a bit like saying that all RWD cars are dangerous just because you've once driven a '92 BMW, disregarding any technological advancements since.
Actually, most of the people I know who think C is a problem that needs fixing are longtime professional compiler developers and people who work on security critical codebases. In fact, I don't know any compiler engineers who don't have serious reservations about C and C++. Those people know more about tooling and instrumentation than virtually anybody. It's precisely that knowledge that leads one rapidly to the conclusion that there are serious flaws in C for secure software that can't just be papered over with tooling.
It's usually C++ enthusiasts who are the ones trying (unsuccessfully, IMO) to argue that undefined behavior isn't a problem in practice.
There was no direct cost to me because I was getting paid to learn this stuff on the job.
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.
That depends on the compiler. It's not true with GCC: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=18501
>it is human nature to forget
High-performance programming is a job the average human does not do. A professional programmer should use spaced-repetition technology to rise above human nature, and use tools like valgrind for extra safety.
Somebody mentioned "scripting languages" - use the ability of scripting languages to construct combinators to write your tests. They migth even emit 'C' code.
This is a hilariously bad attitude for any software that other people will use. When software crashes, people lose work and time. When software has vulnerabilities, bad guys take advantage of them and build stronger botnets. "The danger" isn't like wiping out when you're pulling a stunt; "the danger" is wasting the good guys' time and empowering bad guys.
>There is a lot of great code written in C, and a lot of crappy code written in C
This is true of any mainstream language, so it's completely uninformative and pre-emptively shuts down the possibility of any meaningful language criticism.
Computers aren't just tools, they're also toys. People use computers for entertainment in varied and sundry ways. What is so wrong with somebody wanting to enjoy hacking around in the low level guts of a system? As long as no lives or livelihoods are at stake, what's the problem?
Although your point is very good in that it weakens (further) the "safety is everything" argument. In my opinion. There is so much mission critical software today that is written in C and C++. That's one reason why "safety, safety, safety!" just isn't as persuasive to me as it perhaps is to others.
Can't you substitute "C" with just about anything in this sentence?
It's all well and good to talk about how "beautiful" a language is, but when people are literally endangered because of totally preventable security vulnerabilities that don't happen in programs written in other languages, it's hard to sway me as to how important this so-called "beauty" is.
(Note: I'm playing devil's advocate here to some extent. My view is that safety is important, but lack of provable safety is not some terrible Demogorgon that we should hide in fear from. I think a lot of the concern over safety is valid, but in some contexts it's just overhyped.)
Which is one reason why I don't buy the "memory safety" argument as a very strong one for adopting Rust. There are other much better reasons to do so for a certain class of programming, in my opinion.
I understand that this is... obscure for some reason and I'm not saying it never happens, but let's be realistic....
Thus if you have a to_upper(char*) function, you don't know what it takes or does without looking it up. Does it uppercase a single character or a whole string? How do you even tell what you were passed without potentially reading past the end of a buffer?
If I happen to have a pointer-to-char and pass it to a to_upper function that operates on strings, it will just write on invalid memory, because C can't distinguish between the two.
extern int toupper(int);
(via #include <ctypes.h>) 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 free()) that is the upper case version of the given string.> If I happen to have a pointer-to-char and pass it to a to_upper function that operates on strings, it will just write on invalid memory, because C can't distinguish between the two.
Um ... how do you "happen" to have a pointer-to-char? And unknowingly call to_upper()? I'm lost as to how this can happen ...
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.
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.
>which still doesn't prove your point because c is still pointing to a NUL-terminated string.
No, it's pointing at a char that happens to be part of a nul-terminated string. The semantic intent of that distinction is entirely lost because C fails to make a distinction. I could easily overwrite that nul, and it would no longer be the case. Then it's suddenly an array of chars, and everything pointing at it is now a new type of thing.
char* s = (char*) rand();
This also will point at a 'nul terminated string' with very high probability. Doesn't mean it is safe to call string functions on it...
>I might ask why the function requires a pointer to char for a single character instead of int frobnicate(char)
You could say the same about any pointer argument. Obviously pointers are useful for a reason. If frobnicate returned a char, I would just end up dereferencing a pointer to stick it back in the string it came from. Whether that is frobnicate's job or it's caller's job is a matter of API design, and should not be determined by C, especially when it makes no preference for any other kind of pointer.
>You are also shifting the argument, because in your original comment I replied to, the function you gave was to_upper
My arbitrary example function name doesn't matter one iota. Get over it, and stop being needlessly dense.
So don't do that.
[0] https://en.wikipedia.org/wiki/Buffer_overflow#Choice_of_prog...
I'm not sure how default access in C or C++ isn't explicitly avoiding checks. By definition "a[b]" is an unchecked dereference. It doesn't get more explicit than "by definition." Of course if by "explicit" you mean "syntax exists that demarcates unchecked access" then C and C++ will never satisfy. I'd argue that's a contrived and artificially narrow use of "explicit" meant, er, explicitly to exclude C and C++ from being acceptable by definition and therefore not terribly fair.
Many of us were enjoying the danger of getting low level with Think/Quick/Turbo Pascal and Modula-2.
(not to say that Turbo Pascal was the only one to do that, just fond memories...)
If that was the way it was done in TP, the BBC micro (which was mentioned quite a bit in the recent HN thread about BASICs on personal computers of earlier years), also had a similar feature. I did use that one a bit. You had to open a square bracket in the middle of your BASIC program (though probably not in the middle of a BASIC statement), write your assembly code (6502 instruction set), and then close the square bracket, IIRC.
D (language) these days also has the ability to mix in assembly, though I haven't tried it yet.
Edited for typos and wording.
It could be just a block, a complete procedure/function with or without prolog.
Also it was quite comfortable to write, just as a plain macro Assembler with Intel syntax, not those asm functions with strange syntax used in gcc/clang.
We considered having all the assembler in separate files better practice...
Which is better depends.
That's not true. BCPL language was specifically designed to get something to compile on a machine with no resources to run safety checks, programming in the large, anything. C tweaked it a bit to run on a PDP-7 and then PDP-11. The problems that are in C are there specifically due to the challenges of non-language experts getting a relic of a language to run on machines with no resources.
Later on, Wirth designed Modula-2 that was safe-by-default where possible (eg overflow checks), low-level, easy to read, faster to compile, allowed integrated assembler, and so on compiled also through a PDP-11. They did whole OS's in languages like that. There were numerous languages like that with similar performance to C but way safer and easier to extend. Then there's languages like SPARK 2014 that let you write code that it automatically verifies free of common errors. As in, they can't happen under any circumstances in such apps rather than whatever you thought of during testing.
Having seen those and knowing C's history (i.e. justifications), a number of us know its problems aren't necessary, are easy to avoid with better design, and you still get most of its benefits. Worst case scenario is wanting that but also wanting benefits of C compilers that received a ton of optimization work over the decades or its libraries. In that case, the better language can generate C code as a side effect and/or use a FFI with interface checks added. Still safer-by-default than programming C by hand.
Heck, there's even typed, assembly languages these days you can prove stuff about. Also work like verification of LLVM's intermediate code. So, even for low-level performance or something, C still isn't either the lowest, safest level you can get. It's just the effect of inertia of years of doing stuff with it as a side effect of UNIX's popularity and tons of code that would have to be ported. Again, you can use that without even coding in C at all past some wrappers. So, people liking safety & simplicity of Modula-2, Component Pascal, etc prefer to avoid it since we know the problems aren't necessary at all. Some want extra benefits of stuff like SPARK or Rust, too.
Truth told: system programmers either never need C or need it so few times it's almost totally unnecessary. Others don't need it at all. So, it's "not necessary for vast majority of things application or system developers work on." ;)
I remember reading some of ID's engine code and admiring how well I could follow it and know what's going on. With C++ and other OO languages, it's much harder.
Don't get me wrong, I'm not going to write my next web app in C, and there's some obvious benefits to the features that C++ offers, but C++ ain't beautiful.
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 small code changes.
Things like std::string are just so much easier to work with that their C counterpart and things like std::filesystem::path simplify (or use the boost one if you don't have C++17 yet) so many things and doesn't even have a C counterpart. I point these out as simple examples but in all the code bases I have worked on there are similar examples, like most games a class to represent 2d and 3d points, which are used to define AxisAlignedBoundingBoxes, which are needed for collision detection algorithms which them selves need several classes to describe.
Then I can build systems of a size and complexity I literally could not comprehend without those abstractions. And then the compiler enforces them for me so other people can use them safely as well. Why is it so much harder to do this in C if C is so much "simpler".
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 headache.
See, for example, https://google.github.io/styleguide/cppguide.html#Exceptions documenting why Google is not willing to allow even something as basic as exceptions to be used in C++ code.
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?
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.
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 operator<< overload for the the thing you actually wanted to output seem like the simplest approaches.
As 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.
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.
Npte though that id hasn't really created an influential game in 15 years and arguably the game of the century (Minecraft) was programmed, badly from what I hear, in Java. People often say that "you can write good C code" without considering what you're giving up in terms of architecture and creativity.
There is a lot of great code written in C, and a lot of crappy code written in C. Because C doesn't protect you from yourself, it exacerbates any design flaws your code may have, and makes logical errors ever more insidious. So in this sense, the quality of the C you write is really a reflection of you as a C programmer, not the shortcomings of the language. Maybe you've been badly burned by C in the past, but keep an open mind and understand that C can be beautiful.