Why Is SQLite Coded in C? (2017)
sqlite.org
sqlite.org
Writing SQLite in "plain C" without all that would, well, simply not work.
I agree that "C with analysis backing" is the best language for SQLite right now. However, it should not be used as an example of "How C is a great language for programming in" unless you are also planning on skillfully using a very significant subset of the tools listed in that document. SQLite doesn't prove C is great; it demonstrates how much additional scaffolding is necessary to wrap around C to get that level of quality, and given the quantity and diversity of tools we are talking about, it is not particularly complimentary to "plain C".
It's not just a matter of skill, either. The cost in terms of money and time needed to develop software in that way is completely impractical in almost any commercial scenario. Aside from some very specific situations, it's not an economically viable way to produce software.
just curious, does this extend to dependent libraries as well ?
Really, we're talking about the same thing like they're two separate things: The software [1] (in this case, SQLite) and the application (whatever tables, queries, etc. you need to solve your problem) are used together. So we build the poorly tested, quick and dirty application on top of the well tested, solid software.
[1] Yes, I realize that "software" is a terribly generic term to use to mean "well designed and tested software" and that "application" is also a terribly generic term to use to mean "hastily designed and untested software". Feel free to mentally substitute your own terms if you have better ones.
And while we are at it: http://www.openbsdfoundation.org https://gnupg.org/donate/ https://www.openssl.org/support/donations.html
Is that software held to the standards of mission-critical software? Probably not.
Second: There's a lot of space between the software which is recognized as being mission-critical and software which is just Bioware's latest patch target, and that software includes things like web browsers and the web server software which hosts your company's website. Given how essential a web presence is to, you know, being able to make money, web server software is hardly disposable, but it is not held to the rigorous standards of mission-critical software.
"apps": entertainment or disposable
"software": serious, mission critical
Aside from the difference in terminology, it sounds like your opinions go in the same general direction.
These are two different things.
I think you're confusing the terms "formal verification" and "verification". Testing is a subset of verification, but not of formal verification.
Any one of the 3 test subsystems the describe is already ahead of well more than 99% of all software projects being developed. How is this funded?
I wonder how this works. For sure even the most dedicated developers will every now and then be on a holiday / concert / drunk.
Incredibly experienced programmers write C without those tools, and with practically no exceptions memory issues, crashes, and vulnerabilities surface if the code is large enough and the project used enough.
An easy example of this is the Linux kernel. They historically had relatively few verification tools and have one of the largest C codebases out there. There's a new vulnerability related to memory safety every two or three weeks. There's a bug fixed due to C memory management at least daily.
None of those bugs would have happened in Rust. Most of them couldn't have happened in well written C++ (whereas this well-written C is chock full of them).
Note as well that the kernel now gets some of the most exhaustive verification of any project (such as from google's syzkaller, etc), and it still has tons of C memory issues.
Meaning that if you are human you will make mistakes.
My point is that people who do know what they're doing still make mistakes. Clearly the tool does not match the reality of human cognition.
You can make mistakes in any language, and they will bite you in the backside any way.
Just because some people prefer to live in padded rooms that doesn't mean they can't or won't get hurt if they decide to pull stunts. Padded rooms just provide them with a false notion of safety that ends up making matters worse.
This is a false equivalency.. It's akin to saying "you can hit your thumb putting in a nail with any tool, so you might as well use a rock not a hammer since they're both bad".
Yes, you can make mistakes in any language, but that's not a reason to ignore modern tooling and advances and continue.
Other languages allow us to build abstractions and solve mistakes in ways which are just impossible with C... and without those abstractions, it's just needlessly difficult to keep all the state in your head and write correct code.
An easy example of this is locking and ownership. In C, mutexes are managed manually and what they hold is by convention. It's easy to write races or accidentally deadlock (again as evidenced by such a bug in the kernel existing every few weeks).
In languages with RAII, python's "with", etc, you can't forget to unlock the lock. It's one less thing to think about.
In languages like rust, it's possible to model in the type system that a given resource can only be accessed while a mutex is locked. Again, one less way to make a mistake.
C has no ability to provide or build abstractions that are this robust.
Using C is, more often than not, like using a rock to hammer in a nail.
> Just because some people prefer to live in padded rooms
With C, people are living in a room where the floor is lava and nails coat most surfaces.
I'll take my room that's carpeted, and if I need to pull a stunt and rip up the carpet, sure, I can do that, but at least I can walk normally most of the time.
I've been a professional C++ programmer for almost two decades (and a C++ amateur for almost a decade before that). I've written large amounts of fairly advanced C++ using the latest standards.
My current job however, is all plain C.
One of the interesting (and, for a C++ fan like me, disturbing) things I've found is that the cognitive load is much lower when writing plain well-structured C.
Sure, you need to remember to release that lock and free that memory but this you can do in a very structured way. What you win over C++ is that you don't need to view each line of code with suspicion ("What happens if this line throws an exception?", "I wonder what this line will cost in terms of performance?", "Could this operation cause a cascading destruction of objects I'm going to access further down?").
I love RAII, yet I've debugged enough crashes where "seemingly innocuous operation destructs some local object that has an owning reference to an object that shouldn't be destructed just yet and BAM!", that I'm beginning to doubt its usefulness outside of an exception-generating language (in C++ it's essential for proper exception-handling).
It's always bothered me how complex of an operation people seem to think this is.
errno = pthread_mutex_lock(&m);
if (errno == 0) {
do_something();
pthread_mutex_unlock(&m);
}
Short of a segfault or 'do_something' doing something really, unusually stupid, you can't avoid freeing the mutex.This is not even remotely the worst thing about mutexes though, so it wouldn't be why I would suggest avoiding them.
There's also the goto pattern, but anytime you separate a lock from an unlock by more than a screen's worth of text, they're forgotten.
Only difference? We were in Python.
The critical question is: what does the error rate look like for code of similar complexity? It's very possible that C programmers will try to keep things simple, because the language doesn't support them and they have to be extra careful. The flip-side is that they probably can't develop projects as complex as C++ would enable them to.
Seems like that padded floor has some rusted nails hiding right behind the pretty fabric.
RAII is not something limited to Rust, or C++, or any other language. The abstraction underpinning RAII can be done and has been done in C; you can see it done repeatedly in the code for collectd.
Its up to the developers to make their programs safe and reliable. No language to date will do that for them.
But languages do make a huge contribution. For example, Rust, Ada and Modula-3 are all much safer by defaults alone compared to C. Most Rust code sits outside unsafe blocks, so the existence of this feature does not prove there is no point to Rust.
I didn't say anything along those lines. I said that it's up to developers to make their programs safe.
Defaults matter, no doubt. But they are not a silver bullet; greater base safety can even cause people to become lax when thinking about safety, resulting in even bigger problems. Why do Python developers accept and parse untrusted pickle objects? Because Python is safe, and they don't have to think about what's going on under the hood.
It's indirectly related to computer programming, but a study was done in Europe which showed that crashes in AWD vechicles were, on average, much more severe than 2WD vehicles. Why? Because of the added stability of AWD, people drove faster in adverse conditions.
If you build software in C, you will build abstractions in C, and debugging / analysis tools help to understand these abstractions.
Without a copy of the language standard, I still can't remember all the intricate details about just arithmetic and comparison operators. Take the simple bitshift operators. What happens when the second operand is negative or larger than sizeof(left)*CHAR_BITS? What if it's equal? And the >> and << operators are not even symmetric in their definitions: left-shifting a negative value is undefined but right-shifting a negative value is implementation-defined! And even for simpler operators like + or >, can you immediately tell me all the integer promotion rules and usual arithmetic conversion rules? What happens when you add two integers with different signedness and width?
I can't and I doubt most programmers can. Try implementing a special C interpreter where every undefined and implementation-defined behavior is configurable and you will see how complicated things are.
As a lover of C, these questions are totally irrelevant to me. What I've taken away is don't do things that do not have intuitively clear semantics. Use bitwise and shift operators only on unsigned numbers. Simple as that. And really I haven't found a need to do anything else. I think many of these complex rules are historical accidents or come from supporting a peculiar architecture or kind of hardware that's no longer relevant. In any case, remembering all these pesky rules is not C. C is simple. Remember how to stay away from the dubious operations and you're fine.
uint32_t foo(uint8_t ubyte) {
return ubyte << 24;
}
Look ma, undefined behavior due to signed integer overflow (on 32 bit targets anyways), despite only unsigned types being declared.And what about the aliasing rules:
char *start, *end;
//This is a function that sets start/end to the start and end of an address range. It takes uintptr_t * arguments
GetAddressRange((uintptr_t *)&start, (uintptr_t *)&end);
*start=3; //Undefined behavior because we have now accessed start as both a uintptr_t and a char *.The second, why do you cast as uintptr_t? It makes no sense to me at all. Yep, it might be a little bit advanced, but it's well known that you should only have correctly typed pointers, or void pointers, or char pointers, to any object. It's not a totally obscure thing, just as most people understand the need for "restrict". I'd say the code looks bad to me at first sight, and even a beginner is unlikely to write it because it's a complicated construction.
You have your point, but anyway the pointer-type rule makes sense and I don't know a better version of the rule that could prevent many unnecessary reloads.
The second is based off of code where an operating system call would get the mapped ranges of a shared memory region. For whatever reason, it took a MemoryAddress which was essentially a pre-c99 uintptr_t defined by the system headers (an unsigned value large enough to hold an address). The casts were there because the compiler warned about incompatible pointer types.
I was never able to convince the engineer who wrote the code (~20 years of C experience) that this code was wrong (it worked fine until intermodule inlining was enabled). Instead we just enabled the compiler option to disable optimizations based off of aliasing rules.
You are both greatly overestimating the degree to which programmers understand C, and overestimating the degree to which programmers who do understand C do not make mistakes. I've had lots of people say that the code in the second example "looks bad to me" but the commit still made it through code review and was being run for 5 years before optimization flag changes caused bugs to appear.
Well then, that's nice no? I don't like to play language lawyer. If there are machines were it can lead to errors, it might be bad machine design. The compiler or the user should work together to catch the error / specify more precisely what is the intent. If we can assume that (ubyte << n) == 0 for any n >= 8, I'm very fine with that, too.
I don't think compilers should exploit every undefined behaviour in the C standard. Some of those might be there only to support rare, quirky architectures.
> For whatever reason, it took a MemoryAddress which was essentially a pre-c99 uintptr_t defined by the system headers
That's inconvenient that the API forces such a strange type to the user. But I think the logical usage is to declare "start" and "end" as uintptr_t then. In this case there should be no problem, or do I miss something?
> You are both greatly overestimating the degree to which programmers understand C, and overestimating the degree to which programmers who do understand C do not make mistakes.
All I can say is I've never had this kind of problem really, so far. The kinds of problems I get by using safe toys on the other hand (e.g. array access only possible through proxy containers) are incredibly more painful, because these approaches are extremely bad for modularity and elegance. You have to specify the container, and also its type, or at least implement all kinds of boilerplate interfaces. This is incredibly restricting.
Everybody makes mistakes and while some memory safety problems definitely happen regularly even to very experienced coders, and are harder to detect than e.g. an exception, they are also the minority of the problems I encounter, even when counting the occasional buffer overrun or similar. Even in terms of data security, I figure more exploits are high-level (i.e. logic errors not catched in a high-level language - SQL injection or other auth bypass) than clever low-level ones. And not every type of program is extremely security-sensitive.
> Well then, that's nice no? I don't like to play language lawyer. If there are machines were it can lead to errors, it might be bad machine design. The compiler or the user should work together to catch the error / specify more precisely what is the intent. If we can assume that (ubyte << n) == 0 for any n >= 8, I'm very fine with that, too.
> I don't think compilers should exploit every undefined behaviour in the C standard. Some of those might be there only to support rare, quirky architectures.
In this case, it would be legal for a compiler to assume that ubyte is less than 128. It's not unreasonable to assume that at some point in the future a compiler writer will discover a way to get a 0.1% improvement on whatever the current artificial benchmark of the day is and implement an optimization for it. The landscape of C libraries is littered with undefined code that used to work everywhere and now doesn't for just such a reason.
>> For whatever reason, it took a MemoryAddress which was essentially a pre-c99 uintptr_t defined by the system headers
> That's inconvenient that the API forces such a strange type to the user. But I think the logical usage is to declare "start" and "end" as uintptr_t then. In this case there should be no problem, or do I miss something?
Yes, the correct way is to declare them as uintptr_t and then assign them to a pointer of whatever type you need. It's not uncommon for systems software to treat addresses as integers in places, because they actually do represent specific addresses. I'd have to re-read the specification, but I think that having it take a void instead of a uintptr_t * would have the exact same issue, so I don't think it's the odd API choice that causes this.
Well, that is one way of looking at it. My personal experience is that if there is an undefined behavior in the C standard, it will be exploited, eventually. I have seen this break millions-of-lines applications during compiler upgrades and/or flag-twiddling. As you can imagine, debugging that problem was a nightmare.
[...]
> Everybody makes mistakes and while some memory safety problems definitely happen regularly even to very experienced coders, and are harder to detect than e.g. an exception, they are also the minority of the problems I encounter, even when counting the occasional buffer overrun or similar. Even in terms of data security, I figure more exploits are high-level (i.e. logic errors not catched in a high-level language - SQL injection or other auth bypass) than clever low-level ones. And not every type of program is extremely security-sensitive.
I believe that you are right in terms of number of exploits, but for the wrong reasons.
I can't find the statistics on this, but from the top of my head, ~20 years ago, 80% of the security advisories were buffer overflows/underflows. At the time, just about everything was written in C/C++.
These days, we see lots of high-level exploits, but I would tend to assume that the reason is that 1/ the web makes it very easy to carry out attacks; 2/ C and C++ have a very small attack perimeter on the web, even if you include Apache/nginx/... as part of the web.
Also, yes, web applications, especially in dynamically typed languages also would require military-grade testing :)
The latter problem[1] is applicable to other languages, including Rust, when they permit such type punning. C supports type punning because occasionally it's useful and necessary. The above invokes UB on Rust, too. Type punning has such a horrible code smell that you don't need unsafe{} as a gateway. Maybe for very inexperienced programmers, but they shouldn't be writing C code in situations where type punning suggests itself anymore than they should be writing assembly or unsafe Rust code.
C has many pitfalls, but I don't think I'm being overly pedantic here. There's no question C gives you a significant amount of rope to hang yourself (sometimes affirmatively but usually by turning its head when you steal some), but all languages give you slack to escape the limitations of how they model the environment--the points of contention are how much slack and at what cost. Arguing that requires more precision.
For example, even if we can agree that automatic arithmetic conversions are on balance a bad idea, it's still the fact that there's some benefit to that behavior, such as making your first example well-defined. That's not coincidental.
[1] It's actually only a problem if that routine actually stores the value through a uintptr_t pointer. If it casts the parameter to pointer-to-pointer-to-char before the store it's fine. You can cast a pointer to whatever you want, as many times as you want, and never will the casting, alone, change the semantics of the code. It's only actual load or store expressions that matter, and specifically the immediate pointer type they occur through.
Where you typically find code like this you do the correct thing--the abuse of function parameter types in this particular manner is usually because of const-ness headaches and the lack of function overloading (though now there's _Generic), and in those cases you're already paying attention to type punning pitfalls because you're already at a point where you're trying to skirt around relatively strict, inconvenient typing. If you're not then, again, you're not going to be doing the right thing when writing unsafe Rust code--and there are many more cases where Rust's strict typing is inconvenient, so arguably there's _greater_ risk with a language like Rust when the programmer is lazy or tired and resorts to subverting the type system.
Moreover, this isn't even a pointer aliasing problem. Pointer aliasing problems occur when the _order_ of loads or stores matters, but because of type punning the compiler can neither determine nor assume that access through variables of _different_ type point to the same object and thus it mustn't reorder the sequence. Not only is your example not a case of a single object accessed through multiple types across dependent loads and stores, it's not even a case of accessing through the same type. Unless you use the restrict qualifier, the compiler always assumes that two variables of the same pointer type alias the same object. Whether it's the _wrong_ type is a different, less consequential problem related to the potential for trap representations or alignment violations. But if the hardware doesn't care you'll get expected behavior; it's not the type of pitfall that threatens nasal daemons.
If ubyte is greater than or equal to 128, then converting it to a signed 32-bit integer (as on a 32 bit machine) and then shifting it left by 24 causes signed integer overflow. This is undefined behavior. Therefore the compiler is allowed to make optimizations that assume ubyte is less than 128.
And you are the person who replied to my earlier post saying "don't do things that do not have intuitively clear semantics." Do integer promotion rules count as intuitively clear semantics? Most C programmers never learned them and therefore the semantics is confusing. For the few C programmers who learned them, the semantics is clearer when they can remember the rules, and confusing otherwise.
Now I hope I've convinced you that there are a lot of such subtle semantics issues in C, and hardly anything is intuitively clear, unless you've been a language lawyer or programmed in C for long enough.
It seems I've never really done "overshifting", otherwise I would have easily noticed that the shifted value is promoted first. If you don't overshift there's no way to trigger the undefined behaviour, even when you shift multiple times - since by assigning to the smaller type the result gets cut down again, effectively leading to the behaviour I'd assumed. I would hardly call this a gotcha.
Where it might be confusing is in a situation like this:
char x = 42;
printf("%d\n", x << 4);
But then again I'm undecided if the behaviour is that bad. It might be even useful.Any real-world project will be developed unter specific time constraints and have a certain budget. There will be a mix of developers and other roles in the team, with varying skills. There will be politics involved and the project will probably be in use for decades, ensuring that all of the above will significantly change during its lifetime.
Under such conditions, C is an abject failure at supporting robust, quality code, because it neither benefits from a culture of safety (like Ada or even Rust) nor does it have any enforceable safety switches or indeed safety-by-default.
Anything that can be screwed up, will be screwed up over the years by someone, some configuration option, procedure or project management decision. Java is on average significantly safer than C or C++, because no matter what happens, the language won't allow certain errors to happen.
C can be assumed to be a hopeless case, but a purely theoretical way of avoiding these kinds of issues is to have a sufficient supply of bsenftner clones which will supervise a project through its development and operation until it's retired from production (assuming bsenftner is as awesome as they say).
I don't make the rules. The scary bit is, often, it works just fine; in many cases, it's cheaper to reboot/relaunch for a 1/100000 crashing bug than paying me to find it and fix it!
Of course, this is also a place where we practice static analysis, continuous integration, code reviews and we're now progressively migrating code to Rust.
I was recently looking at bedrockdb, and other DB built on top of it.
Anyone knew what happen to UnQLite ? By the same author.
Look at the types tests listed in the executive summary... which of these can be obviated by language features?
I'll try to break it down:
* Out-of-memory tests: no, all languages run out of memory. The explicit nature of memory management in C might help here, since there's no mystery about exactly where the failure might occur.
* I/O error tests: no, IO failures are independent of language.
* Crash and power loss tests: no, this has nothing to do with language.
* Fuzz tests: yes, partially. Different languages can reduce the range of invalid input a program could encounter.
* Boundary value tests: yes, partially.
* Disabled optimization tests: no, the optimizations being referred to here is a feature of SQLLite, which would need to be tested, regardless of language.
* Regression tests: no, this is independent of language.
* Malformed database tests: partially, because some languages are better at validating input statically, which covers some of the cases of malformed databases.
* Extensive use of assert() and run-time checks: I say no. Yes, asserts are code you need to write, and a cognitive load. But navigating the built-in language constraints of more type- and value- validating languages are also a cognitive load. I believe asserts are often a lessor load since they can be in the problem space of the software being developed vs. the problem space of the language design. In C the molehill comes to the mountain. OK, Ok, on the other hand, in C no one forces you to write good asserts. But then, in no language does anyone force you to write code that actually does the job.
* Valingrad analysis: yes
* Undefined behavior checks: yes
So, largely no. Other languages solve some problems, yet have their own drawbacks.
You're drawing a sharp distinction between "C" and "C with analysis backing", yet there is no such distinction in the real world. We see in this project what we would see in every successful, long-running project (and do NOT see, 100%, in every project that failed in the long-run): controls in place to deal with the potential pitfalls. One advantage to C is that it's history can give you some nice guidelines to understand when you have pretty good coverage on those, from a language standpoint.
I mean, I'm not arguing that some modern languages have their advantages. But, they have disadvantages as well. And, their advantages -- restricted as they are to static analysis -- solve little problems, not the bigs ones.
Also, while I'm sure you don't intend it this way, simply listing the issues like that is a bit visually deceptive in that our hindbrains tend to assume all those issues are roughly equally weighted, because they're all roughly equally the same size. But the distribution of issues there is very non-linear. C doesn't just have a bit of an issue with undefined behavior; it is nearly (but not quite) uniquely damaged by it.
Hypothetically some type systems could partially cover some of the things you've labelled as "no", but I'll aggressively beat you to the punch that such things are very hypothetical, except I'll be saying it with sadness and through grinding teeth, because I find it frustrating how thoroughly our environments ignore some issues like that. But that's a rant for a century I don't expect to live to see.
You did more than that: you've suggested that C was only usable if a pile of safety infrastructure by providing sqlite as your proof, and once that claim was debunked you kept insisting on your baseless assertion.
> I'm just saying that you can't use SQLite as proof that C is A-OK okey dokey
There is no need to prove that because it's quite obvious that C is fine. If you feel the need to prove otherwise then you need to put forward your own proof. Either you support your baseless assertions with rational and tangible claims or you're just venting an irrational dislike. Meanwhile the world runs on C and has been running for decades.
> it's OK to just keep programming in it forever, because it does not use C. If you're not putting in that much effort to your C codebase, then you're not getting those results.
You're somehow turning a blind eye to the fact that that "effort" is patently language-independent and has absolutely nothing to do with C. Either you somehow missed the whole point of the post or for some reason felt the need to keep repeating baseless claims that were already debunked.
I have no idea why you've developed an irrational hatred of C, but it clearly is a personal issue, not a technical one.
No. It is quite obvious to anyone rational and without a vested interest that C is no longer fit for purpose in this modern age.
> I have no idea why you've developed an irrational hatred of C
Let me guess. He's sick of patching security holes, memory leaks and crashes?
I'd agree with you, except that just this morning I was reading a paper about the NARCISSUS framework that can automatically generate encoders and decoders that provably conform to a given specification, and can be extended by a user without rewriting or modifying the framework itself. The authors even patched a networking stack (in MirageOS) with the not-especially-optimized ML extracted from their generated code and showed a performance hit that could be considered acceptable for real-life use cases.
https://www.cs.purdue.edu/homes/bendy/Narcissus/
With advances like this in program synthesis and formal verification, plus real progress in machine learning, it feels like we're living in the start of the age that was _supposed_ to happen during the golden age of AI.
(Excuse the gushing hyperbole. My coffee must have been stronger than usual this morning.)
My point is, if you're not putting that much effort into your codebase -- whatever the language you're not getting those results. There is no language that makes developing and maintaining SQLLite easy or straightforward.
That's pretty awesome actually.
I could be wrong but I think the above poster hasn't actually written tons of C, or else they would have picked a much more complicated example. If you throw a bunch of arrays, pointers, and function pointers in it quickly gets out of hand, like this:
int (* const(*foo)(int (*)(int, int))[5])(void);
That thing defines 'foo', which is a pointer to a function who's first argument is a pointer to a function return a int and taking two ints as arguments, and returns a pointer to an array of 5 const function pointers which take no arguments and return an int.Thankfully, you rarely run into something like that in the wild. And if you `typedef` the function pointers (Which is a pretty common now) it becomes tons easier to read.
I find this much easier to understand than something with different keywords and qualifiers. Function pointers follow some simple rules to read, but can you be sure of what `volatile` implies ?
Hmm, I suppose we just disagree then, which is fine. Things like `restrict` and `volatile` don't really bother me because:
1. The spots where people actually need to use them are very rare (`restrict` does have uses, but almost nobody uses it commonly. `volatile` has pretty much no correct uses outside of accessing registers in low-level code).
2. It's still just a pointer to a long long. I would need to look-up what __declspec((dllimport)) does, and I'd be highly suspicious of the use of `restrict` and `volatile` if it was not explained, but I still generally know what it is and how it can be used. Whereas with the example I gave, without staring for a long time or throwing it into a parser I can hardly tell what the type even is, let alone wrap my head around how it will be used.
Sorry, but what are you guys on about? You use "volatile" if you don't want the compiler to produce code that caches a variable. You use it all the time in embedded systems, or when writing multi-threaded code, etc. The usage of "volatile" is very clear, and should be well understood. "restrict" isn't used much because it's a c99 standard first of all, and second of all, it's like "inline"; it might make a difference, might not, and not a lot of people are in a situation where they have to shave off cycles.
Thanks for providing a very nice exhibit of the confusion around this. Volatile is 99% useless for multithreading code (unless on MSVC which has a peculiar interpretation of the standard). See https://stackoverflow.com/a/4558031/1495627
No amount of fences or mutexes is going to help you if threads are operating on their own version of a variable that got cached in a register, or optimised away. You need to understand volatile to write correct multi-threaded code.
That StackOverflow answer is bogus, along with all the other comments. It's just a rant that, albeit correct, is barely related to the question asked, and it's a clear example why you shouldn't treat StackOverflow as more than an unreliable help forum. The guy asked about whether you should make variables shared inside of a critical section volatile. The answer is "yes, to prevent threads from working on stale data".
Any properly written Mutex or similar implementation is going to act as a full memory barrier and compiler barrier, meaning variables will already not be cached across lock/unlock. And if you're not properly taking your locks before accessing your variables, `volatile` is not going to save you. The fact is, if you're using a lock to protect a variable, marking it `volatile` gains you nothing and just slows your code down.
> "yes, to prevent threads from working on stale data".
`volatile` absolutely does not guarantee a variable doesn't contain 'stale' data. That's the entire reason you need memory barriers in the first place. Even through the compiler will read a `volatile` variable from memory every time, that memory may still have a stale value in the CPU cache, which `volatile` will do nothing to prevent. Only proper use of memory barriers ensures everyone is working on the same thing, which `volatile` does not do.
Yes, it will! Just because you’re accessing shared variables doesn’t mean you need locks. This is what volatile is for!
You’re blindly hoping the compiler will do the right thing for you, anyway. You must have little-to-no experience writing multi-theaded code in production, let alone anything more advanced that doesn’t rely on locks.
Nobody’s arguing that volatile somehow bypasses the cache. When it comes to multithreading, that doesn’t matter though, because the whole process works with the cache, and the CPU keeps core caches synchronized. Memory barriers do nothing here.
Seriously, you’re arguing rubbish.
FWIW, it doesn't even do this, it's just a compiler-level annotation:
volatile int x = 0;
int foo() {
// read
int ret = x;
(void)x;
// write
x = 0;
x = 1;
return ret;
}
The 'volatile' ensures that that code results in two reads and two writes. Removing it allows the compiler to optimise down to the equivalent of 'int ret = x; x = 1; return ret;', but both with and without use the exact same read/write instructions (i.e. have the same interaction with the cache): mov on x86 and ldr/str on ARM.volatile's one of the simplest modifiers to understand if you care to actually learn the language. It has a clear, and well-defined purpouse, and i have no idea how you can spend so much time, and energy arguing that that's not the case.
None of it makes the language inherently any more complex, any more than representing machine instructions by long strings of binary makes _them_ any more complex.
With a boatload of associated semantics, quite a bunch bein in practice compiler-dependent (looking at you, MSVC volatile).
And a "protected abstract virtual base pure virtual private destructor" does not mean anything -- or at least any code that compiles.
Which is not part of C. Your argument that "C is complicated" doesn't work if you cite an egregious misuse of nonstandard extensions.
What about the zinger, “if you think English is not overly complex, just what is a loud old fast tall red car, and when was the last time you needed one?” No language I’m familiar with limits the number of adjectives you can use for a single noun. That doesn’t mean you generally should string several adjectives together; usually you’re fine just saying “car,” but sometimes you need the ability to be specific. English is a complicated language, but limiting how many adjectives can apply to a particular noun would make it much worse in my opinion.
As for the original question, a pure virtual function is a function that must be implemented in a derived class, while a private function is a function that can’t be accessed outside of the class, not even from derived classes. A destructor is a function used to clean up when an object goes out of scope; I don’t think anything about the destructor is important in this case, it just happens to be a convenient member function to use.
The combination would make it impossible to instantiate objects of the class (because of the pure virtual function) or objects of classes that inherit from it (because the pure virtual function can’t be accessed, let alone implemented, from a derived class). That rules out much of what you’d want to do with a class. You’re left with static data and functions, and nested types. Overall, the best I can tell, you would use that to make the class imitate a namespace. But for the last twenty years, you don’t have to imitate namespaces: C++ has them.
You might still find somebody trying to avoid using actual namespaces because of a vague fear of argument dependent lookup, but to be honest, I’ve never seen argument dependent lookup call the wrong function. Some programmers oppose it in principle, but in practice it doesn’t seem to cause much trouble.
(Though, C does have its share of mental burdens. Understanding automatic type conversion is quite a beast.)
OTOH preprocessor directives grow the base C a bit. It's still very tiny, though.
Like all abstractions there are corner cases that force you to deal with the underlying complexity.
Sure, C++ is double of that, but it's still long. In comparison, Java language specs is around 800 pages, Ruby 330 pages, and C# 500 pages.
Subtle UB, as much of a headache as it can be to a programmer using the language, in this case leads to simpler implementation of a compiler.
simplicity for the user and simplicity for the compiler writer are totally different things. Java is simpler for the user but more complex for the implementer, and C is more complex for the user but simpler for the implementer.
The rules for TBAA are complex, and many C/C++ programs violate those rules because it's so hard to actually make sure you're not violating them, and half of the purpose of using C/C++ is actually to be able to do the kind of type-punning that TBAA prohibits.
C's memory model is trivial by comparison.
To be fair, that now includes a substantial standard library. The language + preprocessor spec ends on page 197 and starts on page 19, for a total of 178 pages.
(Why the lack of formal semantics? My guess is that all of these languages, to varying degree, are too complex to easily formalize...)
What about Haskell?
This only applies if you're writing 100% of the code. Not having coworkers to mess around with every corner-case of the langage is a rare privilege.
The same could be said about Lisp. Would you trade your C compiler for Lisp one?
I think C's apparent simplicity lulls me into complacency at times, I delude myself into thinking that I'm coding into some kind of macro assembly and I think I know what the resulting machine code will look like. And then some super weird optimization or UB kicks in and nothing makes sense anymore, because I stopped playing by the rules and I triggered the footgun.
Just look at the number of bug reports on the GCC bugtracker for code that at a glance ought to work and it turns out that it's actually not a bug, the code just triggered a subtle UB and the compiler ran away with it and generated code that ate your cat.
I don't see the point of your claim regarding undefined behaviour. The rules are quite simple: undefined behavior means compiler-specific behavior. Therefore, if you aim for compiler independence then you don't use it. If somehou you decide yo target a compiler then you read the compiler's docs. It's that simple.
These UB complains are even more ridiculous when we realize they complain about the fact that the language is actually defined.
I think the point was that UB occurs in a lot of relatively common cases and programmers don't realize that they're depending on it/experiencing it.
> If [somehow] you decide [to] target a compiler then you read the compiler's docs.
Which is why the post you're responding to made the point about undefined behavior. "Read the compiler's docs" is in counterpoint to that post's parent, which praises C for being a small language, and thus one in which reading the language's docs is seldom required.
There are "implementation defined" details in the C standard but it's a different problem, see for instance: https://gcc.gnu.org/onlinedocs/gcc/C-Implementation.html
Anyway that wasn't really my point, the problem is that some of these UB can arise because of subtle bugs in code that might not look suspicious at a glance. Things like breaking aliasing rules, mis-using unions, casting things that aren't compatible etc... Your code triggers an UB and you don't know it. Actually you might not notice it until you turn an optimization flag or you update your compiler and suddenly it doesn't do what you want anymore.
Even something as trivial as computing a pointer that's more than one byte after the end of an object is UB for instance (not dereferencing it, merely computing its address). For that reason `ptr.offset` in unsafe in Rust for instance, even though it doesn't dereference the pointer.
fn main() {
// Look, an invalid pointer, no `unsafe` required.
let ptr = 1000 as *const u8;
// boom, segfault.
println!("{}", unsafe { *ptr });
}
Using casting one can even implement a "safe" pointer offset function, like so: fn main() {
fn safe_offset<T>(ptr: *const T, offset: isize) -> *const T {
((ptr as usize).wrapping_add(offset as usize)) as *const T
}
let xs = [0u8, 10];
let ptr = safe_offset(&xs[0], 1);
println!("{}", unsafe { *ptr }); // prints '10'
}
Obviously this "safe_offset" function can easily be used to trigger UB by computing invalid pointers, and not a single line of unsafe code was required (although we do need `unsafe` to dereference the bad pointer and actually trigger segfaults).>When casting from pointer to integer and back again, the resulting pointer must reference the same object as the original pointer, otherwise the behavior is undefined. That is, one may not use integer arithmetic to avoid the undefined behavior of pointer arithmetic as proscribed in C99 and C11 6.5.6/8.
https://gcc.gnu.org/onlinedocs/gcc/Arrays-and-pointers-imple...
That's pretty explicitly what Thiez was doing in their rust code, although obviously Rust/LLVM might have different semantics here.
SQLite is arguably the best of what has happened to the world of desktop/embedded databases ever yet lack of alternative (not necessarily this fast) implementations in high-level languages is its main problem IMHO.
For instance, Windows binary deployments are quite predictable (and often dead simple; so easy a lot of Windows installers get it wrong and yet still work), but on *nix you may have to fight distro differences in libc or get a dozen different answers on what you should statically link in your compile versus what you "must" dynamically link and a half-dozen different installers/bundlers/distro tools to deal with making sure the shared libraries are both installed and correctly linked to. What "just works" is often relative and the magic is very easily dispelled in that terrible state of "why isn't this working?", especially if it "just works" like magic for other people.
For what it is worth, the Windows UWP platform even bundles a shared SQLite install (as like Android and iOS) and using it is quite easy in a bunch of languages (just not easily portable cross-platform).
That's only if you're working with C / C++ though. Switch to Go, Java or basically nearly any other language, and most of your cross platform headaches go away.
Though for what it's worth, half those problems aren't really problems you'd generally have to get your hands dirty with as your tooling and distro should manage that for you (just so long as you write POSIX code that is). I'll grant you I've not done anything too complex in C++ but what I have done was portable between Linux, Solaris and FreeBSD (the 3 systems I needed those POSIX C++ programs to target).
That was compiling on those respective platforms rather than compiling for those platforms then packaging them up for deployment. I've written a lot of code over the last 30 years and shipped it in a plethora of different ways and creating installers has historically definitely been easier on Windows. No question. Go lang is helping somewhat in that it's now really easy to ship a dependency free binary. But Windows is only one platform and one that creates more problems for cross-platform portability than all of the rest of the big platforms put together.
It's neither predictable, nor simple nor easily portable when it's about a mixture of managed (C#, Java etc.) and native (C, C++, asm etc) code. I'm happy to know the Windows UWP platform finally has it built-in but I code WinForms and console apps targeting Windows 7 + Mac and Linux with Mono and using SQLite always means a problem (yet I always want to use SQLite to store anything (except just some things that obviously fit other ways of storage better) because it's great and it's a standard supported everywhere).
Are you saying that there's no actually working .net sqlite bindings?
Because when using python, featuring an sqlite database is roughly:
import sqlite3
with sqlite3.connect(db_file) as cnx:
# query the db> even if it actually starts working nobody really knows how to deploy such an app on another computer (especially if it runs a different kind of OS) correctly.
emphasis mine.
(and portability could obviously be solved the same way it was in Python: ship sqlite as part of the core system)
There is nothing stopping you from statically linking it with the rest of your code.
Good luck using portable libraries.
Apart from compiling it using something like ilcc, that is.
It's not entirely cross-platform, but it is possible. Example: https://developers.redhat.com/blog/2016/09/14/pinvoke-in-net...
More details in the .NET Standard: https://docs.microsoft.com/en-us/dotnet/standard/native-inte...
[1] One interesting reference site: https://www.pinvoke.net/
That seems a bit much to simulate static linking, but doesn't sound like it would be too noticeable to users.
You could probably even make it cross-platform that way.
Nowadays, you mostly can ignore x86, but I guess you still hit similar problems if you want your code to run on Windows for ARM, .NET core on Linux, etc.
Also, isn't SQLite just supported out-of-the-box by new .Net versions? E.g. https://docs.microsoft.com/en-us/ef/core/get-started/netcore....
Edit: full disclosure, I am a maintainer :-)
That being said, Rust and possibly even Go would be strong contenders to make a new SQLite-like library/program today. At least on the Rust side, the C bindings are excellent too.
Building from source is usual, of course, given the lack of a stable ABI.
It does however mean that there is an issue with supported platforms. Rust has support for all the major platforms already, but C is probably the most widely supported language in existence at all.
Of course, it's not usual Rust code, but no binary that small is usual code, even in C.
The point is to demonstrate that you can strip Rust down to as small as you want/need, not to suggest that every single Rust program is ultra-tiny.
Doubtful, rust and go do not run on nearly as many platforms as c and sqlite does. I get everyone wants to use other languages, but this incessant "rewrite in rust/go/$THING" is getting annoying.
I'm going to try to coin a new online discussion rule of programming language posts:
- At some point someone is going to suggest any problems for one language can be solved by rewriting in another language.
In this case "because c" "therefore rust". Its basically godwins law for programming language discussion.
But you'll have to overcome and provide a good reason for why anyone using sqlite should move to your new unproven shiny.
If anything sqlite is a poster child for c done (and tested/validated) right. You'll have to demonstrate a lot more than you can implement some things better in the new language.
Go is a good language, one of its core pieces is to increase memory safety through the use of a garbage collector.
Much of what the SQLite post states may not be safe operations in Go.
Rust would be a safer alternative that meets many of the SQLite requirements.
The thing is, SQLite is bulletproof at this point, so do we need to replace it?
No, probably not, it is fine as it is. Still, even SQLite started out as a "for fun" project. Who knows what might happen in the future to displace it? ;)
I wish I had intended that pun embedded in the comment, but from now on ;)
Those aren't mutually exclusive. I for one find destroyers to be an absolute riot.
See https://changelog.com/podcast/201#transcript-100
>I had couple months off and I thought, "Hey, I'm just gonna go and cobble together a really quick and simple database engine that just does a few very simple SQL commands, insert the lead, update and select." No joints, wasn't trying to be efficient... All I needed to do was pull stuff off of a disk in that memory.
>And I put it out there and... I've been doing open source for years before this, putting things on my website, and people would find my thing -- or well, you know, I'd put things on my website and it'd get like five downloads per year, or something like that. I'd figured this would be just another one of those things, but for whatever reason it really resonated with people.
https://www.sqlite.org/testing.html
There's no question that sqlite is one of the most well understood and reliable codebases in the world.
Rewriting just for the sake of rewriting or because you like another language better is an almost certain recipe for disaster.
[1] MC/DC coverage is a slightly more rigorous form than branch coverage. A condition if (a && b) requires that you only test one of (a false, b true) and (a false, b false) for 100% coverage, whereas MC/DC coverage would insist on both being tested.
"we are all guilty"
Nothing written in C is bulletproof.
Personally, I think it'd be better to just start afresh with new implementations, not necessarily under the auspices of the SQLite project. Being 'compatible' is reasonable enough and you can just ditch the lesser used or out of date features if you're not claiming to be a 100% clone.
The core is where the real compatibility issues lie and where it makes the most sense to translate, as well as where the core vulnerabilities that can be remotely exploited lie.
Literally every system has a C compiler, except maybe for a very small number of very old and niche systems. Assuming it cound fit on the ROM, sqlite could probably be ported to my z80 calculator, which has an 8 bit processor and 128 kiolobytes of RAM - trivially! C runs literally everywhere, which is something no other programming language can lay claim to. For a tool like sqlite there is no other choice, period.
Our platform support is limited by LLVM though, this is a good reason for sure.
You have a hell of a lot of control over its performance and behaviour. Are there instances where you found that wasn't the case?
As far as the understanding of C goes, I can agree with that. But we are discussing rewriting sqlite in something else after all.
Seeing as Go was suggested above (which is also garbage collected), I figured I would mention Nim too.
Love to hear what those are and what language you would consider developing SQLite in.
But, I tried working in Nim without GC and out turned out to be a bigger hassle than I expected it to be. GC was supposed to be ref counted and now they're doing memory regions? There is also a -gc:stack which I'm not sure if it works the way like in C++. Then there is the pointer free paradigm. Don't get me wrong, I'm very optimistic about Nim and would be great if it really replaces C. But I feel like it's doing too many things at once.
I personally feel like Nim dev team should stop adding new features every release and work on releasing a solid 1.0.
Regardless, those are called "landing pads", and you can compile with an option to turn them off. Many do.
In the present day context of vulnerabilities it's tempting to blame C. Yet it's still a good choice for many reasons. It's not wise to suggest that everything written in C must be re-written in some other language because of hand-waving reasons like buffer-overflows or off-by-one errors.
Maybe you could prove Rust is a good choice by writing your own SQLite implementation feature for feature? Until then I don't think there's going to be a compelling reason to re-write SQLite because it's written in C.
A person undertaking that task would already have a head start in that they could likely use the extensive test suite for SQLite ;-)
These security vulnerabilities are trivial to fix at the language level but cause extremely negative consequences if exploited successfully. Yet the default stance of C programmers is to not adress them at all. A competitor like Rust absolutely becomes necessary because of the complacency of C programmers. They will then will strongly critizise the newcomer that is built on strong fundamentals as "language of the week" that is chosen by "dumber programmers" that are trying to ride the latest hype train.
If the problem is complacent programmers, switching languages is going to have no effect.
You're also making blanket statements about the attitudes of C programmers which have nothing to do with the C language. That probably means your generalizations are overly broad and not true, not even for a significant fraction of C programmers.
I think you need to use some logic to argue your point, not wild claims.
Please, find the vulnerability in that code, or quit making wild claims
I know, theoretically it's possible to write secure code. Even in C. Experience showed, that 99,999% of people aren't able to do that. And many reasons have its origin in the design of this bad language.
Engineering is trade offs. I think the SQLite programmers are well aware of the risk the use of C brings in the context of security vulnerabilities. Given the statistics collected here: https://www.cvedetails.com/vendor/9237/Sqlite.html it seems that their choice of C was not a security disaster, because C. It actually seems well within a risk tolerance threshold that I don't sleep uneasy at night recommending its use in systems. If their choice of language came with unavoidable vulnerabilities I'd expect those statistics to be much worse.
So what does a Rust implementation of SQLite offer anyone? That within 8 years there may be one less SQL injection attack or perhaps < 4 overflow vulnerabilities? Will it have better interoperability or performance than SQLite does now or will have once this new version is done? Will the market care?
I'm not saying that care shouldn't given when choosing to use C in a greenfield project today. I am saying that for many problem domains the risks associated with C are tolerable given a well trained, disciplined team.
And I doubt there's much of a market for an in-memory database system that is going to be feature compatible with SQLite in a few years that is also as performant as SQLite and can deployed on as many platforms. But that's just a prediction... maybe I'm wrong.
I've chosen Haskell on a greenfield project recently for many of the safety guarantees it brings... who knows?
EDIT: Jokes aside, Rust sounds like the most plausible alternative, though it still lags C due to being LLVM-only, whereas C has gcc and... others.
I'm curious though--is it true that Go brings its runtime? I'm of the impression that the runtime is only compiled in if you actually use it, but if your library is just `func Add(a, b int) int { return a + b }`, would linking against it still bring in the runtime?
And to reiterate, this is only a curiosity. Even if you could take care to avoid importing the runtime, I wouldn't think that it's worth the while.
In terms of performance, it's really the only language out there that can claim to be as fast as C/C++. It has a minimal runtime, with the option of no runtime. It has pretty great FFI and can produce easily callable libraries for other runtime-heavy languages.
It's not, however, as broadly compatible as C. I don't think that's a problem for most cases, as most programming is now done for (MIPS|ARM|X86|X86_64), and it can handle those well enough. But microcontrollers and OS-less embedded devices still have a ways to go before Rust beats out C.
And stability is just not there. IMO, that's a good thing. Rust is the best thing to happen to systems programming in a really long time, and there are still tons of ideas with amazing potential benefits. The Rust community has been exceptional at guiding this development. I'm sure some day it will level off, but until then, I'm happy with it changing pretty rapidly.
but not necessarily posix/windows/etc
having C as a least-common-denominator toolchain which almost all platforms will provide is still useful for the embedded world where sqlite has a huge number of applications..
It's story around panic and debug handling is also improving, including cutting out formatting code for things like println! and debug! when targetting embedded platforms.
Here's exactly what I was told -- "This was achieved by manually unrolling a 10-step loop, which compiler apparently could not optimize."
The conclusion that C remains the best language is a lot harder to support. Certainly, and especially with the level of infrastructure that SQLite developed to harden its implementation, the alternatives are not so much better as to be worth the cost of migration.
Such as macOS ;)
Go absolutely would not be, one of the purposes of sqlite is to be embeddable in any and all software.
I've done such conversions with C to Rust converter (https://gitlab.com/citrus-rs/citrus), but quickly found out that the style of writing idiomatic in C is part of the problem.
To replace code function by function you're generally forced to keep the same structs and APIs (often even internal ones) for most of the time, and these require you to erase the extra type safety, degrade smart pointers and slices to plain pointers, etc.
If you just do all the same wonky stuff that C does, but only with a slightly different syntax and compiler, you don't gain that much. The value comes from using idioms of a safer language, and that's much more work, and it's especially hard if your hands are tied by the rest of the program being C-like.
That's not much of an argument considering the CLR and the JVM are written in C.
Candidates may be: C, C++, Rust and maybe Swift(if the deterministic ref-counting doesnt get in the way).
Of course you can do it in other langs, but it wont be able to compete with products made in languages with better fitness to this.
Eg. CouchDB vs. MongoDB. The first invented the concept, but the later used a language with better fitness for the kind of problem, and therefore was able to create a better product.
The great thing about C is that when someone shoots you in the foot you know who it was.
In the end, I came to the conclusion that it's far more productive (for me atleast) just to stick with plain C and using a 'helper' library like Apache Portable Runtime library.
do you also take this into account when leveraging $HIGH_LEVEL_LANGUAGE's libraries that are written in C ?
The normal solution to this is for C++ libraries to expose an interface in plain C, which is much easier for other languages to call. However, this either restricts what you can do in your implementation (because you're stuck with the C "subset" of C++ for anything near the API), or you have to maintain wrapper code mapping C function calls to your actual C++ interface. Neither is great, so plain C ends up making a lot of sense for a library that is expected to be called from different languages.
SQLite is meant to be basic infrastructure that is wrapped by a variety of other languages. It should have a lowest-common-denominator interface, and C is good for that. And while it might be easier to implement it in $FAVORITE_LANGUAGE, SQLite is mature and well-tested. In some sense it's "finished," and throwing it out and replacing it would be a waste of time.
As few people seem to realize, it's common for even the C standard library to be implemented in C++. Having to implement all of the printf variants (there's 8, I think, at least) using C macros is horrible. Instead, the actual implementation of printf/fprintf etc happens in a function template. You then have one line extern C functions implemented via calling this template, which are declared in the header (and defined in the .cpp, along with the template).
> The C language is old and boring. It is a well-known and well-understood language.
...I think they are very fundamentally mistaken. C is a horribly complicated language. It is one of the least-understood languages out there. Experienced programmers and compiler authors can debate for hours about whether C code of less than 50 lines has defined behavior or not, and still not come to a conclusion. People can write an entire PhD thesis <https://robbertkrebbers.nl/thesis.html> studying the semantics of C, and still leave many open question (chapter 2 of that thesis does not require any academic background to be understandable, and it comes with tons of links to tickets/questions filed against the C standard). Consistently writing safe C/C++ is near impossible <http://robert.ocallahan.org/2017/07/confession-of-cc-program..., and judging from <https://sqlite.org/testing.html> the SQLite team agrees.
C is old, yes -- and C has boring and well-understood fragments. But full C is very, very poorly understood.
> Libraries written in C doe not have a huge run-time dependency. In its minimum configuration, SQLite requires only the following routines from the standard C library:
memcmp()
memcpy()
memmove()
memset()
strcmp()
strlen()> In a more complete build, SQLite also uses library routines like malloc() and free() and operating system interfaces for opening, reading, writing, and closing files.
> In its minimum configuration, SQLite requires only the following routines from the standard C library:
memcmp() memcpy() memmove() memset()
strcmp() strlen() strnc
I once used sqlite in a embedded system project that was based on a VLIW processor(Trimedia). The only compiler available was a C compiler. The integration worked like a charm.
Writing Makefiles by hand works for small projects, but for bigger ones you're forced to choose the lesser of all evils. And if you're on linux you'll still have to deal with glibc most of the time.
And of course you can only choose between compilers that do too much (as in, not "C compilers" but "compilers that happen to support C as well"), compilers that don't even aim for standards compliance, or proprietary compilers.
I have spent about three weeks going deep in the autotools rabbit hole (mostly converting FOSS projects that used other build systems and then ran into trouble) but coming out the other side I can say it's not as bad as it looks, and compared to other systems it could be much, much worse.
As was posted earlier, calling C a "portable assembly language" seems right, and having a portable system to go with it to build and have common functionality isn't all that strange when you think about it that way.
While other systems might be easier for a lot of cases, not much out there can get you where the classic stack of C + Autotools + (g)make + glibc/musl gets you. As with everything, it's a tradeoff between quality, cost (As in time/knowledge) and options.
I'm not sure if I understand, but almost all mainstream compilers treat 'C' as a first class citizen. Even MSVC, notorious for only supporting C89 finally caught up and now supports most of C11.
huh? What compiler supports C and Perl and Bash? WTF?
Sure, most C compilers are also C++ compilers. I'm not sure why this would be surprising to anyone. I don't even know how you would create a C++ compiler that didn't also compile C.
Take it as my obsession for minimalism.
well, it's able to handle whole operating systems so... https://github.com/reactos/reactos
i don't have a solid gauge for how much of that was because of my own inexperience or the tool itself, although my gut feeling is that it's probably a bit of both.
Why is this a problem?
Sure, some way or another all those can fall under the category "list directory contents", but I'd rather use separate programs for every one of those.
Programs that I will install if and when I need them.
It seems to be actually a simple build configuration tool. A ton of projects are using it, including systemd.
I consider that a bit of cheat, because it replaces the problem of embedding one C program (SQLite) with the problem of embedding a different C program (Lua).
But for those whose objection is that writing the database logic in C is risky because C is too low level, this would put the database logic in a higher level language than C.
This page shows that SQLlite is 5 times faster than Win10 filesystem, but the performance gains are much small in other systems.
Can anyone comment on this?
But I have to admit that the biggest downside of programming in C is people make their projects complicated. I saw some projects that have compilcated Makefile build systems and have hidden definitions. The worst part is that they leave these hidden stuff UNDOCUMENTED. This is often very frustrating when contributing open source projects.
I don't know an easy solution to this as most C compilers seem to be platform specific. In general, I like C and even more SQLite. Nevertheless, it is quite unfortunate if you can't make use of the modern cross platform compilers.
Maybe one day we will live on a world where cross compiling isn't an issue anymore.
So compile SQLite for your target platform and use the bindings?
The HN web server and application are written in a dialect of Lisp, unless something has changed.
Giving the great design it have and the leverage of Google on mobile phones, it will probably get a massive deployment in the future.
Also, the important bits of the HN "web server" are the business logic that makes HN HN, and that is written in a Lisp dialect.
Even if there weren't counter-examples: are the languages that browsers, OSes, and web servers written in somehow more important than any other languages? Are browsers/OSes/web servers the best emblems of reliability engineering to you? There are a lot of interesting assumptions at play here.
Since so many people depend on them, I'd say yes. It's extremely obvious to users when they don't work, so I'd assume there's a lot of work put into making them reliable.
Suggesting to drop all of std just to customize just one feature of the allocator it is not a good solution. You should know harsh OOM handling is a problem for Rust users, and there's work being done to improve it.
> You should know harsh OOM handling is a problem for Rust users
A small number of people have this issue. It barely even applies on entire operating systems, for example.
That said, custom allocators will be nice.