Subtleties of the ANSI/ISO C standard [pdf]
open-std.org
open-std.org
"Furthermore, we argue that the C standard does not allow Turing complete implementations, and that its evaluation semantics does not preserve typing. Finally, we claim that no strictly conforming programs exist. That is, there is no C program for which the standard can guarantee that it will not crash."
And it seems to be written with a serious tone, it is probably not even a joke!
I mean, if you want to judge ridiculousness, then their explanation of why there are no strictly conforming programs (specifically: even a program with an empty main function cannot be guaranteed not to overflow the stack) is also ridiculous, but it's still interesting.
I agree that none of this has any obvious relevance to the practical usefulness of C, but all of us here in this thread have chosen to put that aside, at least for now, in order to contemplate a somewhat esoteric though less practical question.
As a small example, consider Brainfuck. The Brainfuck specification (if such a thing exists) has no upper bound on the number of memory cells, but practical implementations generally do have a limit that "useful" Brainfuck programs will rarely hit. The specification describes a TC language, while the implementation does not.
Therefore, if a language is TC according to its specification, but has an implementation in a language that is shown to not be TC, then it follows that the implementation must be of a non-TC subset of the language. This is, of course, assuming both that the specification has been proven TC, and that the implementing language has been proven non-TC.
I believe the point being made in the paper is that the C specification itself is restrictive enough that it describes a non-TC language. In other words, any implementation of C that is truly Turing complete would be violating the specification in some way. In that case, it follows that a C implementation of a TC language (such as the bignum language) must only be an implementation of a non-TC subset of the language.
(As an aside, consider that nothing running on an isolated machine will ever be truly TC, since an isolated machine has a finite amount of memory and so can't be TC. It's not that much of an inferential jump to see that this kind of TC-breaking limitation could end up in the specification of C, a language quite close to the metal.)
The C standard does not make any guarantees about the size of the call stack. But any program program needs to rely on this implementation-defined property (cf a pathological implementation were the initial call to main() already overflows) and thus cannot be strictly conforming.
Why doesn't the C standard guarantee a minimum stack size? Because there could be tiny microcontrollers with just 64 bytes of stack on which you can implement some C subset. At the same time, the standard doesn't want to define specifics of stack frame layouts, so, not knowing how much memory is available on your target machine, and not wanting to constrain how a stack frame for some source function is laid out as part of the C standard, it's impossible to compute how deep you can go on your call stack without architectural details, it's implementation dependent. It makes a lot of sense that the standard is the way it is, from an engineering point of view.
While I agree with most of what you wrote, it sounds like you are blaming the standard. Seems to me the proper blame is the unreasonableness of the language-lawyering by compiler engineers...which as you point out is a fairly recent phenomenon.
I always like to say that my first C compiler was pre-ANSI-C, so by today's language-lawyering all behavior was undefined and therefore the compiler could have done anything at all. Yet for some strange reason the code produced was more predictable than today.
Seriously. There's a basic concept involved here called "Quality Of Implementation" which effectively guarantees that the programs produced by a compiler will work as long as you stay within reasonable bounds.
Might those bounds be a bit constrained on embedded systems? Sure. Them's the breaks. However, having a stack size of zero is not worth worrying about.
https://tdotc.wordpress.com/2012/11/20/on-the-turing-complet...
First off, they say that C isn't complete because its functions for reading/writing files are "bounded". Since most other programming languages are really just implemented in C, wouldn't that mean that either ALL programming languages aren't Turing complete, or that C is?
Secondly, they mention that programming languages that have "bignum" types get around the "bounded" memory problem (because you can access "infinite" memory). But you can implement a bignum in C...
I hope this is a joke.
No: it means that the imperfect emulation of those programming languages, as implemented in C, will fail to be Turing complete. Put another way, there are programs that are valid Haskell programs that can be proved by the rules of Haskell to halt on certain inputs and generate particular results, which will not be able to execute as the C emulator will run out of state in which to perform the simulation. The interesting point to make here is that it isn't just that C is being limited by the real world constraints of hardware, but that C is itself limited as if it were hardware. That said, this was always an obvious result to anyone who ever bothered asking the question and understands the formalisms, so I am not certain of the contribution of this paper ;P.
In general I would agree with authors on this point as long as we exclude external storage. They also hint that, while it would be trivial to provide interface to infinite memory tape, the standardized part of file I/O does not seem to do that. They claim that it is prohibited by bounded return value of ftell. I would disagree, because ftell may return an error value in this scenario as well. Moreover to access infinite tape you don't even need ftell, relative movement provided fseek suffices.
It would have been more interesting if the author discussed Microsoft’s solution to this problem, namely __try/__except and EXCEPTION_STACK_OVERFLOW, which is actually used by programmers to recover from stack overflow.
I remain unconvinced that memcmp of pointer values is at all interesting (viz the pointer normalization dancing acts of the early x86 memory models, ugh).
From a working programmer's point of view, it's useful to know some of the points in the paper; just read the middle third and skim the rest.
However the arguing that C is not Turing complete after you take away the IO and limit yourself on an non-abstract machine is silly. C can simulate any Turing machine limited by the same previously mentioned rules and the same holds if you remove those rules for both systems, not just for one.
read returns 0 or 1. write only uses the least significant bit.