
C Is Not Reasonable - ingve
https://www.osr.com/blog/2016/09/19/c-reasonable/
======
wott
First line:

> ULONGLONG tableOffset;

It's starting well, for a C example...

Oh, and it ends even better:

> _I write drivers for a living, not scientific or statistical analysis
> software. During this project, I quickly learn that casting everything to
> (double) was my friend. When in doubt, stick a (double) in front of it and
> test it again. In the end, the code worked pretty well. The customer was
> happy. We got paid._

o_O

OKOK, so you write drivers but don't know about C types, standard types,
fixed-length types, you randomly fiddle with types until it passes a test but
you don't know why it passes or doesn't, and you're proud of selling that.

Wonderful.

EDIT: added the "even better" part.

~~~
OskarS
As someone who only has a passing knowledge of C, why is that wrong? Is it
because it should be "uint_64" instead?

~~~
xgbi
Because ULONGLONG is reminiscent of Windows, Visual Studio, and its flaky and
outdated support for C.

It does not remove the argument of C choosing to use the type of the operands
to determine the width of the calculus, but it shown that this is not
portable, standard C.

Also, if the guy writes drivers, you should expect him to be aware of these
problems, possibly having built an extensive set of preprocessor macros to
handle these problems, like all other drivers use. But he seems to just loop
into the "modify /compile" until it compiles and behaves, without trying to
understand.

~~~
omginternets
To be fair, though, this entire comment thread echoes the main point of the
article: in what world is it reasonable to have to keep track of such things?

~~~
falcolas
Drivers, for one. Any world where performance is so critical that you want to
ensure you're fitting as much information as you reasonably can into the L1
and L2 caches for the computations you must do.

There's even still a role in this world for people to fine tune the assembly
code for a specific execution environment to make their HPC models execute
faster.

~~~
omginternets
Respectfully, this misses my point (which, granted, was poorly explained).

The necessary evil here is using low-level languages. The unnecessary evil is
using a language like C with arbitrary and highly variant conventions.

Better alternatives exist for writing drivers (e.g. Rust). To be clear,
historical baggage sometimes dictates that we must use C, but that doesn't
change the fact that much of the C world is a footgun, leading to eye-gouging
frustration and threads such as this one about the myriad pitfalls associated
with the language.

The author might not be a good C programmer, but this thread nevertheless
supports his point. The necessity of writing C _in practice_ is neither here
nor there.

~~~
falcolas
Rust and its ilk _may_ eventually eat this space, but it doesn't appear ready.
For one, it's list of supported architectures is too small. For two, it's
poorly optimized, when compared to C. Three, it's still too new - it has only
been "stable" for a bit over a year. Four, the static verification tools are
not yet there (you still need a way to verify the unsafe portions of code,
which will not be trivial in a driver).

Do you know of any existing non-toy (i.e. distributed in support of a device)
drivers which have been written in Rust? I'd love to see some in action, and
will be happy revise my opinion of Rust's capabilities when those drivers
start rolling out.

Let's be frank for a moment. Rust's biggest advantage in this space is its
memory safety. But we've had memory safe languages for as long as we've had C,
and they still have not eaten C's lunch. If Rust wants to pave the way into
C's territory (and not just C++'s territory), it will need to identify why and
address that.

~~~
omginternets
Okay, point taken, but I'd like to back-pedal a bit if you don't mind. I agree
that Rust is very immature, but again I think I misspoke.

The basic point I'm trying to make is that C is (a) fraught with historical
baggage that makes it difficult to use safely, in practice and that (b) this
comment thread proves this point.

All points made beyond this are secondary and tangential. Again, I have to
agree with you about the maturity of Rust, but I must insist that using C is
fraught with absolute insanity. Sometimes it's the only thing available, but
it's still insane.

------
merraksh

      ULONGLONG tableOffset;
    
      tableOffset = (l1Index * L1_TABLE_GRANULARITY) +
                    (l2Index * L2_TABLE_GRANULARITY) +
                    startingL3-&gt;StartingOffset;
    

I'm tempted to point out that there's some HTML mishap, but actually the
statement will be compiled if there is a variable called gt and one called
StartingOffset.

------
pjc50
Arithmetic is surprisingly hard to get "right". You might try to generalise
from this example that "multiplying two N bit numbers together should give a
result 2N" wide, then discover that for simple examples you run out of machine
bits. Then there's overflow/saturation handling, which is a mess everywhere:
lots of systems have hardware support for saturation arithmetic, but you can't
conveniently specify it in C.

If anyone could think of a good, concise way of _expressing_ all these bells
and whistles of arithmetic, it could be implemented as a language or language
frontend. For now, most languages choose to either ignore it entirely or push
the user to floating or arbitary-precision arithmetic as an 'improvement'.

~~~
lmm
For modern applications programming, arbitrary-precision is probably the right
way to do integer arithmetic. Python does that, and it doesn't seem to cause
any trouble. The people who need to do massive amounts of numeric stuff know
who they are and can take the time to learn the relevant arcana, but your
typical cat pictures app never has to worry about how big any of its integers
are.

~~~
masklinn
> Python does that

So do Ruby or Erlang. The problem, of course, is that it has a cost: trivial
arithmetic operations have to be checked and may need to allocate.

And operational coverage can be spotty outside of the trivial range e.g. when
you give a bignum to an "integer" operation going through FPN, bad things can
happen as said FPN are generally machine doubles (fp64)

------
haberman
It is strange to pick on C for this, as I cannot think of a single language
that works the way the author seems to want. The only exception I can think of
is Perl, which has the "wantarray" function that lets a function vary its
behavior based on what its return is being assigned to:
[http://perldoc.perl.org/functions/wantarray.html](http://perldoc.perl.org/functions/wantarray.html)

With that exception, it's pretty much always assumed that an expression is
evaluated independently of what it is being assigned to. I have a feeling that
the author's preferred behavior would lead to some surprising results, but I
can't think of any good examples off the top of my head.

~~~
nradov
It would be nice if languages allowed us to overload function names with the
exact same parameters but different return types. The compiler or runtime
environment should be able to select the correct implementation automatically
based on type inference, or allow the programmer to specify which one should
be used with an annotation. This would make some code a little cleaner instead
of having to give all of those functions different names.

~~~
fredmorcos
> or allow the programmer to specify which one should be used with an
> annotation.

That's exactly:

> give all of those functions different names.

~~~
chriswarbo
Effectively; although "name" usually implies an opaque symbol, e.g. the
language would see no difference between, say, "addAsInts" vs. "addAsFloats",
compared to "addAsInts" vs. "divide".

Classes, namespaces, modules, etc. allow names to have a more fine-grained
structure, e.g. "int.add" and "int.divide" come from the same module, whilst
"int.add" and "float.add" are alternative implementations of the same
signature.

~~~
fredmorcos
> whilst "int.add" and "float.add" are alternative implementations of the same
> signature.

Maybe in a functional programming language like Haskell. But not in C++.

~~~
chriswarbo
The syntax was just an example ;)

What I described is actually closer to ML.

~~~
fredmorcos
Has nothing to do with syntax.

------
tedunangst
"This is wrong, and there's a way to fix it, but shut up I don't want to
learn."

ok...

~~~
topspin
Indeed. The post begins with a question and asserts that the reader doesn't
know the answer. I knew the correct answer instinctively and I haven't written
any C in anger in years. Expecting a C compiler to promote expression operands
prior to assignment because the output location is a larger size... that's
just not the mentality C programmers develop; what if the output is smaller?
Should all the expression operands be demoted automatically, producing
radically different results as large numbers get truncated _prior_ to
evaluation? No. Obviously not. That sort of thing is self-evident to a
competent C programmer.

------
fredmorcos
I really couldn't organize my thoughts from the sheer amount of criticism I
have of this blog post. So instead, I'll kindly ask anyone to give this person
a "do what I mean, and not what I say" programming language.

And they will realize that even that has quirks.

------
cm3
C is a minimal abstraction over asm and I wouldn't be surprised if this
behavior can be configured in some C compiler, even though it would be odd to
do so.

What I find surprising is when people say C is a simple and small language
that's nice to build portable programs in. So much of C is undefined behavior
or implementation specific that many professional users pin a certain compiler
toolchain version for a project. Part of it is due to changing and hard to
predict optimizer passes, but most of it is due to the purported portability
feature of C. I mean, if I leave a lot of the semantics undefined or to be
defined per compiler+target, then it's not really a portability abstraction.
Not to mention the missing or, if you consider POSIX, inconsistent across
platforms, C stdlib.

It's educational to look into Modula-3 and SPIN. SPIN is like MirageOS but
with dynamic loading of components whereas in Mirage everything is compiled
ahead of time into the final image. I mention Modula-3 because it's of similar
age as ANSI-C and serves as an example of an OS written in a comparatively
safe language around the same time Unix won and gave us the hegemony of C
(with all the avoidable security fallout ever since).

Even though software isn't like the real world and could be improved
substantially unlike world politics, we still continue dealing with bugs due
to C, although we have better options. I think as long as new operating
systems or libraries and applications are written in C, this won't change.
Just the idea of writing kernels for IoT systems in C in 2016 makes no sense
from a reliability and high assurance perspective. Getting there half way
would be exposing hard-to-misuse C APIs where the implementation is in a safe
language (Rust, ATS2, etc). For the moment consuming C APIs is the cross
platform library interface we have to deal with, but having at least the
implementation be safe would go a long way.

~~~
fredmorcos
C portability is not portability in the Java or Python sense. It's portability
across architectures, and undefined (and implementation-defined) behavior is a
powerful tool for that.

~~~
cm3
Of course, but shifting the burden onto developers, where most of them aren't
professional libc or kernel engineers, leads to avoidable issues.

Rust seems familiar enough to many developers, and forces them to think about
resource management before running the code by not allowing incorrect code, so
I hope it will lead to more libraries and applications that would have
otherwise been easily plagued with issues due to choosing C. Granted, I'm not
a fan of how Rust's surface has turned out, but it's a sensible compromise to
attract masses of developers into writing less buggy code that operates on the
same level as that written in C. So I use it as a C replacement, and for that
use case I like it because there's momentum behind it.

------
hyperliner
I am just going to leave this here:

[https://www.amazon.com/Programming-Language-Brian-W-
Kernigha...](https://www.amazon.com/Programming-Language-Brian-W-
Kernighan/dp/0131103628)

------
rayiner
The way to understand it is that "+" when applied to ULONGs returns a ULONG.
What the heck else it would return, in a language that doesn't have arbitrary
precision arithmetic?

~~~
pcwalton
If it were consistent with FLT_EVAL_METHOD==2, then it would evaluate in the
highest precision available.

There's a lot of smug criticism of this article, but there actually is a
reasonable point that FLT_EVAL_METHOD is inconsistent with the way integers
are handled in C.

~~~
rayiner
That's a good point.

------
marcosdumay
What harsh comments about a person that is just asking for a warning. Tell me,
do you really think such a warning wouldn't be reasonable?

I do agree with the author that it would be better if C coerced the operators
into the result type before the calculation instead of after. I would really
like a warning when there's an implicit coercion at the end of a calculation.

------
byuu
Programming practically requires a willingness to continue learning. But his
statements are deeply troubling:

> I’m not annoyed by the way statements are formed, or even by the precedence
> order (which I readily admit to not knowing or understanding or even caring
> much about)

> And don’t complain about how I parenthesize my arithmetic statements. I
> already mentioned precedence order. All those parens are the result of yet
> another lesson I learned to avoid working weekends.

... his example is saying (a×b)+(c×d)+e->f. I understand it being a learning
curve to memorize that bit-shifts have higher precedence over bitwise
operators, but ... you learn that multiplication happens before addition in
elementary school! It's a pre-requisite to learning pre-Algebra in middle
school. This isn't even some arcane programming thing you have to learn.

Sure, you _can_ pepper all your code in parentheses, but sooner or later
you're going to come across code that doesn't. And a person like that working
on such a codebase is a huge danger.

I don't say this to be mean, but I really don't think programming is the right
profession for this guy.

~~~
chriswarbo
Whilst the delivery could be better, I agree with the sentiment that operator
precedence is a waste of time. It's exactly the kind of mundane, error-prone
work that machines should be doing for us, whether it's via a sophisticated
structured editor, or a simple hack like Emacs's various paredit-like modes.

> Sure, you can pepper all your code in parentheses, but sooner or later
> you're going to come across code that doesn't. And a person like that
> working on such a codebase is a huge danger.

I can certainly imagine someone who doesn't care about precedence using an
editor which disambiguates such things automatically; either by adding
parentheses, colouring the background, etc. I can also imagine such a person
spotting precedence bugs introduced by a colleague who considered themselves
to be above such tooling.

Lisp, Forth and friends do perfectly well without having to consult precedence
tables, and whilst I appreciate that some would prefer more syntax than those
provide, I think precedence rules should still stick to the meta-level,
parser-directing stuff like block delimiters, statement separators ("x ; y"),
and maybe syntax sugar like "," ".", "=", etc. If it can be written as a
library (which certainly includes things like numeric procedures) then it
shouldn't have any precedence.

Even this minimal amount of precedence should be avoidable if desired, e.g. to
avoid ambiguities like "a = b xor c". In PHP this parses to "(a = b) xor c",
which is a perfectly valid PHP expression and caused the most egregious waste
of my time to date.

Whilst it's true that learning and applying "BODMAS" is simple enough to do in
high school arithmetic, the context of high school arithmetic is vastly
impoverished compared to computer programming.

For example, which has the higher precedence: integer multiplication or list
append? What about floating-point division compared to image convolution? Tree
construction or matrix subtraction?

I think such questions are silly, yet they're a legitimate concern in
languages with heavy use of infix notation (e.g. Haskell). Languages which
define a fixed set of infix operators to avoid such problems (e.g. C) cause an
unnecessary asymmetry between operators and every other procedure (e.g. think
about the number of times "function(x, y) { return x + y; }" has been written
by Javascript programmers!), and are impoverishing their domain of discourse
much like high school arithmetic (e.g. compare introductory C material to
something like
[https://code.world/doc.html?help/codeworld.md](https://code.world/doc.html?help/codeworld.md)
or
[http://www.bootstrapworld.org/materials/spring2016/tutorial](http://www.bootstrapworld.org/materials/spring2016/tutorial)
)

~~~
masklinn
> Lisp, Forth and friends do perfectly well without having to consult
> precedence tables

It helps that they have "alien" syntaxes (prefix or postfix but not infix).
Smalltalk is also a language which did away with precedence almost entirely:
it has a strict right-to-left evaluation model, and only three precedence
levels[0]: unary messages (aSubject aMessage), binary messages (aValue
<operator> anOtherValue) and keyword messages (aSubject aMessage: aValue) but
because it has "infix operators" the effect is much weirder.

[0] or possibly a fourth, the cascading `;` operator is not a message, and can
become really, really weird when used with different message types e.g.

    
    
        7 + 4 squared; factorial; yourself.
    

binds like this:

    
    
        7 (+ (4 squared)); factorial; yourself.
    

so it computes 4 squared, adds it to 7, then computes the factorial _of 7_ and
finally returns 7.

------
Kenji
It's really simple, I have a table hanging right by my screen that has type of
Operand1 and type of Operand2 and then the type of the result. It has never
been an issue. In general, the result is always cast towards the larger and
unsigned type.

------
jayeshbadwaik
implicit conversions bad stop always use casts for conversion stop do not
write drivers for my hardware with this approach stop

------
pskocik
So true. A page long rant is much more reasonable than the ten-or-so
keystrokes that would paste the ULONLONGs in there.

