La respuesta a su pregunta depende de qué lenguaje C está preguntando.
El lenguaje descrito en el Manual de referencia C de 1974 de Dennis Ritchie era un lenguaje de bajo nivel que ofrecía algunas de las ventajas de programación de los lenguajes de nivel superior. Los dialectos derivados de ese lenguaje también tienden a ser lenguajes de programación de bajo nivel.
Sin embargo, cuando se publicó el estándar C 1989/1990, no describía el lenguaje de bajo nivel que se había vuelto popular para programar máquinas reales, sino que describía un lenguaje de nivel superior que podría ser, pero no era obligatorio, -implementado en términos de nivel inferior.
Como señalan los autores de la Norma C, una de las cosas que hizo útil el lenguaje fue que muchas implementaciones podrían tratarse como ensambladores de alto nivel. Debido a que C también se usó como una alternativa a otros lenguajes de alto nivel, y debido a que muchas aplicaciones no requerían la capacidad de hacer cosas que los lenguajes de alto nivel no podían hacer, los autores del Estándar permitieron que las implementaciones se comportaran de manera arbitraria si los programas intentaron usar construcciones de bajo nivel. En consecuencia, el lenguaje descrito por el Estándar C nunca ha sido un lenguaje de programación de bajo nivel.
Para comprender esta distinción, considere cómo Ritchie's Language y C89 verían el fragmento de código:
struct foo { int x,y; float z; } *p;
...
p[3].y+=1;
en una plataforma donde "char" tiene 8 bits, "int" tiene 16 bits big-endian, "float" tiene 32 bits y las estructuras no tienen requisitos especiales de relleno o alineación, por lo que el tamaño de "struct foo" es de 8 bytes.
En el lenguaje de Ritchie, el comportamiento de la última declaración tomaría la dirección almacenada en "p", le agregaría 3 * 8 + 2 [es decir, 26] bytes y buscaría un valor de 16 bits de los bytes en esa dirección y la siguiente , agregue uno a ese valor y luego vuelva a escribir ese valor de 16 bits en los mismos dos bytes. El comportamiento se definiría como actuar sobre los bytes 26 y 27 que siguen al de la dirección p sin tener en cuenta qué tipo de objeto estaba almacenado allí.
En el lenguaje definido por el Estándar C, en el caso de que * p identifique un elemento de un "struct foo []" seguido de al menos tres elementos completos más de ese tipo, la última instrucción agregaría uno al miembro y de El tercer elemento después de * p. El comportamiento no sería definido por el Estándar bajo ninguna otra circunstancia.
El lenguaje de Ritchie era un lenguaje de programación de bajo nivel porque, si bien permitía a un programador usar abstracciones como matrices y estructuras cuando era conveniente, definía el comportamiento en términos del diseño subyacente de los objetos en la memoria. Por el contrario, el lenguaje descrito por C89 y estándares posteriores define las cosas en términos de una abstracción de nivel superior, y solo define el comportamiento del código que es consistente con eso. Las implementaciones de calidad adecuadas para la programación de bajo nivel se comportarán de manera útil en más casos que los exigidos por el Estándar, pero no existe un documento "oficial" que especifique qué debe hacer una implementación para ser adecuada para tales fines.
El lenguaje C inventado por Dennis Ritchie es, por lo tanto, un lenguaje de bajo nivel, y fue reconocido como tal. Sin embargo, el lenguaje inventado por el Comité de Normas C nunca ha sido un lenguaje de bajo nivel en ausencia de garantías proporcionadas por la implementación que vayan más allá de los mandatos de la Norma.