Ed Prochak said:
I should have phrased this: C is LIKE an assembler.
And a raven is like a writing desk.
<
http://www.straightdope.com/classics/a5_266.html>
"C is an assembler" and "C is like an assembler" are two *very*
different statements. The latter is obviously true, given a
sufficiently loose interpretation of "like".
The C standard doesn't distinguish between different kinds of
diagnostics, and it doesn't require any program to be rejected by the
compiler (unless it has a "#error" directive). This allows for
language extensions; an implementation is free to interpret an
otherwise illegal construct as it likes, as long as it produces some
kind of diagnostic in conforming mode. It also doesn't require the
diagnostic to have any particular form, or to be clearly associated
with the point at which the error occurred. (Those are
quality-of-implementation issues.)
This looseness of requirements for diagnostics isn't a point of
similarity between C and assemblers; on the contrary, in every
assembler I've seen, misspelling the name of an opcode or using
incorrect punctuation for an addressing mode results in an immediate
error message and failure of the assembler.
To some degree you are right. It's actually pointer manipulation that
makes it closer to assembler.
C provides certain operations on certain types. Pointer arithmetic
happens to be something that can be done in most or all assemblers and
in C, but C places restrictions on pointer arithmetic that you won't
find in any assembler. For example, you can subtract one pointer from
another, but only if they're pointers to the same type; in a typical
assembler, pointer values don't even have types. Pointer arithmetic
is allowed only within the bounds of a single object (though
violations of this needn't be diagnosed; they cause undefined
behavior); pointer arithmetic in an assembler gives you whatever
result makes sense given the underlying address representation. C
says nothing about how pointers are represented, and arithmetic on
pointers is not defined in terms of ordinary integer arithmetic; in an
assembler, the representation of a pointer is exposed, and you'd
probably use the ordinary integer opertations to perform pointer
arithmetic.
How about a bitsliced machine that uses only 6bit integers?
What about it? A conforming C implementation on such a machine must
have CHAR_BIT>=8, INT_MAX>=32768, LONG_MAX>=2147483647, and so forth.
The compiler may have to do some extra work to implement this. (You
could certainly provide a non-conforming C implementation that
provides a 6-bit type called "int"; the C standard obviously places no
constraints on non-conforming implementations. I'd recommend calling
the resulting language something other than C, to avoid confusion.)
Forgive my memory,but is it PL/1 or ADA that lets the programmer define
what integer type he wants. Syntax was something like
INTEGER*12 X
defined X as a 12 bit integer. (Note that such syntax is portable in
that on two different processors, you still know that the range of X is
+2048 to -2047
The point is a 16bit integer in ADA is always a 16bit integer and
writing
x=32768 +10
will always overflow in ADA, but it is dependent on the compiler and
processor in C. It can overflow, or it can succeed.
I'm not familiar with PL/I.
Ada (not ADA) has a predefined type called Integer. It can have other
predefined integer types such as Short_Integer, Long_Integer,
Long_Long_Integer, and so forth. There are specific requirements on
the ranges of these types, quite similar to C's requirements for int,
short, long, etc. There's also a syntax for declaring a user-defined
type with a specified range:
type Integer_32 is range -2**31 .. 2**31-1;
This type will be implemented as one of the predefined integer types,
selected by the compiler to cover the requested range.
C99 has something similar, but not as elaborate: a set of typedefs in
But my point on this was, you need to know your target processor in C
more than in a language like ADA. This puts a burden on the C
programmer closer to an assembler programmer on the same machine than
to a ADA programmer.
You can get just as "close to the metal" in Ada as you can in C. Or,
in both languages, you can write portable code that will work properly
regardless of the underlying hardware, as long as there's a conforming
implementation. C is lower-level than Ada, so it's there's a greater
bias in C to relatively low-level constructs and system dependencies,
but it's only a matter of degree. In this sense, C and Ada are far
more similar to each other than either is to any assembler I've ever
seen.
[...]
a big characteristic of assembler is that it is a simple language.
C is also a very simple language. Other HLLs are simple too, but the
simplicity combined with other characteristics suggest to me an
assembler feel to the language.
If you're just saying there's an "assembler feel", I won't argue with
you -- except to say that, with the right mindset, you can write
portable code in C without thinking much about the underlying
hardware.
[...]
No I was talking about the original motivation for the design of the
language. It was designed to exploit the register increment on DEC
processors. in the right context, (e.g. y=x++
the increment doesn't
even become a separate instruction, as I mentioned in another post.
The PDP-11 has predecrement and postincrement modes; it doesn't have
preincrement or postdecrement. And yet C provides all 4 combinations,
with no implied preference for the ones that happen to be
implementable as PDP-11 addressing modes. In any case, C's ancestry
goes back to the PDP-7, and to earlier languages (B and BCPL) that
predate the PDP-11.
[...]
I know that it is just a suggestion. The point is Why was it included
in the language at all? Initially it gave the programmer more control.
Sure, but giving the programmer more control is hardly synonymous with
assembly language.
Which makes sense to an assembler programmer, but not to a typical HLL
programmer.
Sure, it's a low-level feature.
lets put it this way. there is a gradient scale, from pure digits of
machine language (e.g., programming obcodes in binary is closer to the
hardware than using octal or hex)
at the lowest end and moving up past assebmler to higher and higher
levels of abstraction away from the hardware. On that scale, I put C
much closer to assembler than any other HLL I know. here's some samples
PERL, BASH, SQL
C++, JAVA
PASCAL, FORTRAN, COBOL
C
assembler
HEX opcodes
binary opcodes
digital voltages in the real hardware.
That seems like a reasonable scale (I might put Forth somewhere below
C). But you don't indicate the relative distances between the levels.
C is certainly closer to assembler than Pascal is, but I'd say that C
and Pascal are much closer to each other than either is to assembler.
You can write system-specific non-portable code in any language. In
assembler, you can *only* write system-specific non-portable code. In
C and everything above it, it's possible to write portable code that
will behave as specified on any system with a conforming
implementation, and a conforming implementation is possible on a very
wide variety of hardware. Based on that distinction, there's a
sizable gap between assembler and C.