La risposta alla tua domanda dipende da quale linguaggio C sta chiedendo.
La lingua descritta nel Manuale di riferimento del 1974 C di Dennis Ritchie era un linguaggio di basso livello che offriva alcune delle comodità di programmazione dei linguaggi di livello superiore. Anche i dialetti derivati da quella lingua tendevano a essere linguaggi di programmazione di basso livello.
Quando fu pubblicato lo standard C 1989/1990, tuttavia, non descriveva il linguaggio di basso livello che era diventato popolare per la programmazione di macchine reali, ma descriveva invece un linguaggio di livello superiore che poteva essere - ma non era richiesto essere - implementato in termini di livello inferiore.
Come gli autori della nota C standard, una delle cose che ha reso il linguaggio utile è che molte implementazioni potrebbero essere trattate come assemblatori di alto livello. Poiché C era anche usato come alternativa ad altri linguaggi di alto livello e poiché molte applicazioni non richiedevano la capacità di fare cose che i linguaggi di alto livello non potevano fare, gli autori dello standard consentivano alle implementazioni di comportarsi in modo arbitrario se i programmi hanno provato a usare costrutti di basso livello. Di conseguenza, il linguaggio descritto dallo standard C non è mai stato un linguaggio di programmazione di basso livello.
Per capire questa distinzione, considera come Ritchie's Language e C89 vedrebbero il frammento di codice:
struct foo { int x,y; float z; } *p;
...
p[3].y+=1;
su una piattaforma dove "char" è 8 bit, "int" è big-endian 16 bit,
"float" è a 32 bit e le strutture non hanno padding o allineamento speciali
requisiti quindi la dimensione di "struct foo" è di 8 byte.
Su Ritchie's Language, il comportamento dell'ultima affermazione sarebbe necessario
l'indirizzo memorizzato in "p", aggiungere 3 * 8 + 2 [vale a dire 26] byte, e recuperare
un valore a 16 bit dai byte a quell'indirizzo e il successivo, aggiungere uno
a quel valore e quindi riscrivere quel valore a 16 bit sugli stessi due
byte. Il comportamento sarebbe definito come agire il 26 e il 27
byte che seguono quello all'indirizzo p senza riguardo per quale tipo di
l'oggetto è stato memorizzato lì.
Nella lingua definita dallo standard C, nel caso in cui * p si identifichi
un elemento di una "struct foo []" che è seguito da almeno altri tre
completare gli elementi di quel tipo, l'ultima istruzione aggiungerebbe uno al membro
y del terzo elemento dopo * p. Il comportamento non sarebbe definito dal
Standard in qualsiasi altra circostanza.
Il linguaggio di Ritchie era un linguaggio di programmazione di basso livello perché, mentre era
permesso a un programmatore di usare astrazioni come matrici e strutture quando
conveniente, ha definito il comportamento in termini di layout di base di
oggetti in memoria. Al contrario, la lingua descritta dalla C89 e successive
gli standard definiscono le cose in termini di un'astrazione di livello superiore e solo
definisce il comportamento del codice che è coerente con quello. Le implementazioni di qualità adatte per la programmazione di basso livello si comportano in modo utile in più casi rispetto a quanto previsto dallo Standard, ma non esiste un documento "ufficiale" che specifichi cosa deve fare un'implementazione per essere adatto a tali scopi.
Il linguaggio C inventato da Dennis Ritchie è quindi un linguaggio di basso livello ed è stato riconosciuto come tale. La lingua inventata dal Comitato degli standard C, tuttavia, non è mai stata un linguaggio di basso livello in assenza di garanzie fornite dall'implementazione che vanno oltre i mandati dello Standard.