156

Currently C is considered a low level language, but back in the 70's was it considered low level? Was the term even in use then?

Many popular higher level languages didn't exist until the mid 80's and beyond so I'm curious if and how the nature of low level has changed over the years.

Thomas Owens
  • 85,641
  • 18
  • 207
  • 307
joeyfb
  • 1,259
  • 2
  • 9
  • 9

7 Answers7

157

This depends on your definition of high-level and low-level language. When C was developed, anything that was higher-level than assembly was considered a high-level language. That is a low bar to clear. Later, this terminology shifted to the point that some would nowadays consider even Java to be a low-level language.

Even within the high-level language landscape of the 70s, it is worth pointing out that C is fairly low level. The C language is basically B plus a simple type system, and B is not much more than a convenient procedural/structured syntax layer for assembly. Because the type system is a retro-fit on top of the untyped B language, you can still leave out type annotations in some places and int will be assumed.

C consciously leaves out expensive or difficult to implement features that were already well-established at the time, such as

  • automatic memory management
  • nested functions or closures
  • basic OOP or coroutines
  • more expressive type systems (e.g. range-restricted types, user-defined types such as record types, strong typing, …)

C does have some interesting features:

  • support for recursion (as a consequence of its stack-based automatic variables, as compared to languages where all variables have global lifetime)
  • function pointers
  • User-defined data types (structs and unions) were implemented shortly after C's initial release.
  • C's string representation (pointer-to-chars) is actually a huge improvement over B which encoded multiple letters into one machine word.
  • C's header files were an efficiency hack to keep compilation units small, but also happen to provide a simple module system.
  • Assembly-style unrestricted pointers and pointer arithmetic, as compared to safer references. Pointers are an inherently unsafe feature but also very useful for low-level programming.

At the time when C was developed, other innovative languages such as COBOL, Lisp, ALGOL (in various dialects), PL/I, SNOBOL, Simula, and Pascal had already been published and/or were in wide use for specific problem domains. But most of those existing languages were intended for mainframe programming, or were academic research projects. E.g. when ALGOL-60 was first designed as an universal programming language, the necessary technology and computer science to implement it didn't exist yet. Some of these (some ALGOL dialects, PL/I, Pascal) were also intended for low-level programming, but they tended to have more complex compilers or were too safe (e.g. no unrestricted pointers). Pascal notably lacks good support for variable-length arrays.

Compared to those languages, C rejects “elegant” and expensive features in order to be more practical for low-level development. C was never primarily a language design research project. Instead, it was an offshoot of Unix kernel development on the PDP-11 minicomputer which was comparatively resource-constrained. For its niche (a minimalist low-level language for writing Unix with a single-pass compiler that's easy to port) C absolutely excelled – and over 45 years later it still is the lingua franca of systems programming.

amon
  • 135,795
149

To answer the historical aspects of the question:

The design philosophy is explained in The C Programming Language written by Brian Kernighan and C designer Dennis Ritchie, the "K&R" you may have heard of. The preface to the first edition says

C is not a "very high level" language, nor a "big" one...

and the introduction says

C is a relatively "low level" language... C provides no operations to deal directly with composite objects such as character strings, sets, lists, or arrays. There are no operations that manipulate an entire array or string...

The list goes on for a while before the text continues:

Although the absence of some of these features may seem like a grave deficiency,... keeping the language down to modest size has real benefits.

(I only have the second edition from 1988, but the comment below indicates that the quoted text is the same in the 1978 first edition.)

So, yes, the terms "high level" and "low level" were in use back then, but C was designed to fall somewhere on the spectrum in between. It was possible to write code in C that was portable across hardware platforms, and that was the main criteria for whether a language was considered high level at the time. However, C lacked some features that were characteristic of high level languages, and this was a design decision in favor of simplicity.

gatkin
  • 1,379
38

In the early 1970s, C was a dazzling breath of fresh air using modern constructs so effectively that the entire UNIX system could be rewritten from assembly language into C with negligible space or performance penalty. At the time many contemporaries referred to it as a high level language.

The authors of C, primarily Dennis Ritchie, were more circumspect and in the Bell System Technical Journal article said "C is not a very high-level language." With a wry smile and intending to be provocative, Dennis Ritchie would say it was a low-level language. Chief among his design goals for C was to keep the language close to the machine yet provide portability, that is machine independence.

For more info consult the original BSTJ article:

Thank you Dennis. May you rest in peace.

20

As I wrote elsewhere on this site when someone referred to the malloc/free memory management pattern as "low-level programming,"

Funny how the definition of "low-level" changes over time. When I was first learning to program, any language that provided a standardized heap model that makes a simple allocate/free pattern possible was considered high-level indeed. In low-level programming, you'd have to keep track of the memory yourself, (not the allocations, but the memory locations themselves!), or write your own heap allocator if you were feeling really fancy.

For context, this was in the early 90s, well after C came out.

Mason Wheeler
  • 83,213
15

Many answers have already referred to early articles that said things like “C is not a high level language”.

I can’t resist piling on, however: many, if not most or all HLLs at the time - Algol, Algol-60, PL/1, Pascal - provided array bounds checking and numeric overflow detection.

Last I checked buffer and integer overflows were the root cause of many security vulnerabilities. ... Yep, still the case...

The situation for dynamic memory management was more complicated, but still, C style malloc/free was a great step backward in terms of security.

So if your definition of HLL includes “automatically prevents many low level bugs”, well, the sorry state of cybersecurity would be very different, probably better, if C and UNIX had not happened.

8

Consider older and much higher languages that predated C (1972):

Fortran - 1957 (not much higher level than C)

Lisp - 1958

Cobol - 1959

Fortran IV - 1961 (not much higher level than C)

PL/1 - 1964

APL - 1966

Plus a mid level language like RPG (1959), mostly a programming language to replace plugboard based unit record systems.

From this perspective, C seemed like a very low level language, only a bit above the macro assemblers used on mainframes at the time. In the case of IBM mainframes, assembler macros were used for database access such as BDAM (basic disk access method), since the database interfaces hadn't been ported to Cobol (at that time) resulting in a legacy of a mix of assembly and Cobol programs still in use today on IBM mainframes.

rcgldr
  • 191
6

The answer to your question depends upon which C language it is asking about.

The language described in Dennis Ritchie's 1974 C Reference Manual was a low-level language which offered some of the programming convenience of higher-level languages. Dialects derived from that language likewise tended to be low-level programming languages.

When the 1989/1990 C Standard was published, however, it did not describe the low-level language which had become popular for programming actual machines, but instead described a higher-level language which could be--but was not required to be--implemented in lower-level terms.

As the authors of the C Standard note, one of the things that made the language useful was that many implementations could be treated as high-level assemblers. Because C was also used as an alternative to other high-level languages, and because many applications didn't require the ability to do things that high-level languages couldn't do, the authors of the Standard allowed implementations to behave in arbitrary fashion if programs tried to use low-level constructs. Consequently, the language described by the C Standard has never been a low-level programming language.

To understand this distinction, consider how Ritchie's Language and C89 would view the code snippet:

struct foo { int x,y; float z; } *p;
...
p[3].y+=1;

on a platform where "char" is 8 bits, "int" is 16 bits big-endian, "float" is 32 bits, and structures have no special padding or alignment requirements so the size of "struct foo" is 8 bytes.

On Ritchie's Language, the behavior of the last statement would take the address stored in "p", add 3*8+2 [i.e. 26] bytes to it, and fetch a 16-bit value from the bytes at that address and the next, add one to that value, and then write back that 16 bit value to the same two bytes. The behavior would be defined as acting upon the 26th and 27th bytes following the one at address p without regard for what kind of object was stored there.

In the language defined by the C Standard, in the event that *p identifies an element of a "struct foo[]" which is followed by at least three more complete elements of that type, the last statement would add one to member y of the third element after *p. Behavior would not be defined by the Standard under any other circumstances.

Ritchie's language was a low-level programming language because, while it allowed a programmer to use abstractions like arrays and structures when convenient, it defined behavior in terms of the underlying layout of objects in memory. By contrast, the language described by C89 and later standards defines things in terms of a higher-level abstraction, and only defines the behavior of code that is consistent with that. Quality implementations suitable for low-level programming will behave usefully in more cases than mandated by the Standard, but there's no "official" document specifying what an implementation must do to be suitable for such purposes.

The C language invented by Dennis Ritchie is thus a low-level language, and was recognized as such. The language invented by the C Standards Committee, however, has never been a low-level language in the absence of implementation-provided guarantees that go beyond the Standard's mandates.

supercat
  • 8,629