I heard several people who, as an argument to claim the need of a programming language that is more problem-oriented and less machine-oriented than C language, assert that C language is just “a more portable assembly language” or “a more readable assembly language”. These sentences may be tolerated only as hyperboles, because they are factually quite inaccurate.
In fact C language is radically different from any kind of assembly language. What makes it so different is its hierarchical structure, missing in every assembly language.
Assemble language programs of any kind, both languages for hardware machines and languages for virtual machines, are just sequences of declarations and instruction of limited complexity, i.e. every declaration and every instruction has a given number of operands.
For example, an assembly language may have the quite simple instruction “sum 1 to the accumulator register”, or the much more complex instruction “take the number contained in memory at the location obtained summing 16 to the contents of register A, take also the number contained in memory at the location obtained summing 12 to the contents of register B, add them and store the result in memory at the location obtained summing 2 to the contents of register C”. The latest instruction has the purpose of summing two numbers. It is conceivable an assembly language that has an instruction to sum three numbers, but no assembly language may have a single instruction to sum an arbitrary count of numbers (say, 7), placed in arbitrary positions in memory.
Instead, C language may accept the following statement:
a = b + c + e + f + g + h + i;
This statement is completely different from anything you may write in an assembly language. Other compositing capabilities of C language regard conditions for flow control, blocks of statements, and hierarchical data structures.
In C language, you may write:
if (a > 0 && b == c || d < e + 3) …
This demonstrates that the conditions for flow control statements (“if”, “do”, “while”, “for”) are arbitrarily complex, while assembly language allows only simple conditions (a > 0, a >= 0, a < 0, a <= 0, a == 0, a != 0).
In C language, there are blocks of statements, enclosed in braces, that may be nested at many levels, while assembly has no such hierarchical code structures.
In C language, there are arrays and structures (“struct”), but there are also arrays containing arrays or structures as elements, structures containing arrays or structures as fields, and so on at many levels, while assembly language hasn’t such hierarchical data nesting.
Perhaps, the two aspects of C language that most resemble assembly language are its primitive data types and the tolerance regarding undefined behavior.
The primitive data types of C language are not well specified, to allow for the implementation of different machine architectures, but the numeric and pointer types match rather closely the addressing modes and the register sizes of the hardware processors. A higher level definition for a number type would be “number between 0 and 999, with up to three decimal digits”.
In C language, dereferencing a dangling pointer, or accessing an array out-of-bounds, or using an uninitialized variable has undefined behavior. Such missing specification is quite understandable and accepted by programmers used to assembly language, as it is just what is expected from an assembly language program, but it is quite error-prone, compared to fully specified languages, like Java and C#.