Keith said:
Ed Prochak said:
Keith said:
[...]
C is an assembler because
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".
Well my original statement was that C was a glorified assembler.
We don't need to stretch the meaning of like to the breaking point.
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).
The point is I can assign an integer value to a pointer. The warning
is a nice reminder, but that is all it is. Assemblers allow integers
to be used as pointers. Many HLL's never allow this. Can you suggest
another HLL that allows simple assignment of integer to pointer? (I
recall PL/M, a version of PL/1 for micros from INTEL. It specifically
for embedded programming so this was a required feature.)
... 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.)
Irrelevant
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.
You are missing my point. It has nothing to do with whether C generates
a diagnostic. And I'll happily accept the cast to be rid of it. Point
is I can manipulate memory pointers in C very easily, nearly as easily
as I can in 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.
An assembly programmer has to take on more responsibility. C provides
some tools to help. In fact, the casting helps to a small degree.
consider writing memcpy() in assembler, C, or other HLL. It is very
easy for Assembler and C. In C it may lead to code that has hidden
assumptions (the topic here) but the code will have a look very much
like assembler. In some HLLs it might not be possible to write a
generic memory copy routine.
Let me put it this way. If you are implementing memcpy(), the very
fastest, most optimized version might be pure assembler. Next would be
a version in C customized for the target platform with close to 100% of
the performance of the assembler version. The C version will be easier
to maintain than the pure assembler, but to get it that close requires
the programmer to think of the consequences of certain programming
constructs. Then you'll get a version in some other HLL which might
run well, but will be measureably below the performance of the C and
assembler versions.
... 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.
Yes in C I need to consider the significance of pointer types. But this
is analogous to assembler opcode options.
MOVB -- move a byte
MOVW -- move a word
MOVL -- move a longword
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.)
Actually I'd assign type short to that integer. Good point though. You
made me see I picked the wrong argument. Back to the machine with 9bit
CHAR. In C the MAX_INT is dependent on the underlying processor and is
allowed ro range up to what the hardware can handle, so instead of
32768, you can go up to 131071 Other HLLs do not let that happen. the
max int in PASCAL is 32767, even on that same machine. A High level
language was supposed to get us away from thinking about the underlying
hardware wasn't it?
I'm not familiar with PL/I.
Okay, I found a web page off the PL/I faq page. here's a comment about
PL/I' independence from the hardware that speaks directly about
dayatypes:
<start quote>
PL/I defines its data types very precisely without regard for any
hardware. For example, an "integer" is defined as having a mode of REAL
or COMPLEX, base of BINARY or DECIMAL, along with the precision that
you require. Therefore, FIXED BINARY(12) declares a binary integer of
12 data bits, whereas FIXED DECIMAL(3) declares a decimal integer of 3
decimal digits. You get precisely what you ask for. The implementers
deliver even if the machine has no support for such data type or
precision.
<end quote>
So I had the syntax wrong (It's been a LONG time since I did PL/I), but
the point is
the value range is independent of the underlying hardware. In C this is
true for the low end. That 6bit byte machine would require extra code
from the C compiler to implement 8bit char types. but on a 9bit byte
machine C happily lets the programmer get access to that ninth bit, not
so in PL/I.
PL/I seems to me better than C in that it reduces the hidden
assumptions. So if you need 12bit integers, you get exactly that with
an exception generated when you hit the limit, no matter what the
underlying hardware can handle.
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.
Ada (yes you're right on the letter case) is similar to C. One reason
is that Ada is supposed to be for embedded systems, so it needs the
flexibility of open-ended data ranges. But it also does a LOT more type
checking than C. I only looked at it years ago, never written any real
code in Ada.
C99 has something similar, but not as elaborate: a set of typedefs in
<stdint.h> such as int32_t, intleast32_t, and so forth. Each of these
is implemented as one of the predefined integer types.
But in Ada you could declare
type Integer_24 is range -2**23 .. 2**23-1;
I don't expect stdint.h has those defined.
You can get just as "close to the metal" in Ada as you can in C.
Yes, Ada was a poor choice here. It's too close to C in its feature
set.
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.
TRUE.
But portability alone doesn't free C from the feel of assembler.
16bit 8086 assembler code still worked essentially unchanged on the 386
processor.
.. 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.
yes, as I said in another post, it is a question 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.
I can understand that view, but on the Assembler to HLL scale I still
put C closer to assembler. Ada does a lot more for you than C does,
AFAIK.
[...]
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.
I never said you couldn't. I like C. I have been using it in various
projects since the early 80's. It is a great language. I guess I'm just
saying you can write better C code knowing its assembler side. Doing
that will help keep you aware of some of these gotchas we've been
discussing.
[...]
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 remembered UNIX started on earlir model, but I thought it was a
PDP-8. I don't recall a PDP-7. But I thought C came after the initial
versions of UNIX.
I'll double check my PDP-11 manual about the increment modes. I would
swear they were both. available.
[...]
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.
If you approach C with an Assembler proing mindset, you can exploint
certain features to your advantage on targetted code. Being aware of
that aspect of C can help you avoid some of those things inwriting
portable C code.
Sure, it's a low-level feature.
No, someone with only a HLL view of coding would look at that
restriction and say
"what the %^&*! Why doesn't this work?"
But someone with assembly experience would understand it immediately.
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.
That's it. We just differ on the scale.
I don't have a stron sense of the distances. I guess I would put C
about equidistant from both assembler and PASCAL. Hm, maybe someday I
will have to come up with a scale for this)
FORTH is a good example too. To me FORTH is more like a VM language.
But I haven't done any programming in it, so I left it out.
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.
And when you HAVE to write non-portable code, C is a better choice
than PASCAL or other HLLs (except maybe Ada).
Great discussion. Thanks for the informative replies.
Ed.