Ono pár takových pokusů bylo. Ale uchytil se až ten Rust.
Google mi ukazuje třeba https://github.com/checkedc/checkedc (Microsoft Research).
A pak jsou tu nástroje jako cppcheck nebo https://clang-analyzer.llvm.org/
Téměř Rust se syntaxí C++ je Safe C++. Odhlédněme od toho, že je to C++ a ne C, protože C je prakticky subset C++. Je to pochopitelně s C++ nekompatibilní, ale je to memory-safe a velká výhoda Safe C++ je interoperabilita, jde z toho přímo volat existující C++ kód, takže by to umožňovalo postupnout migraci s využitím existujícího kódu.
Lifetime objektů se řeší stejně jako v Rustu, používá to borrow checker. Problém je, že C++ steering committee to prakticky pohřbila a odmítla se tím zabývat, i když dostala funkční prototyp i s compilerem na stříbrném podnose. Místo toho tlačí steering committee profily, které ale neudělají z C++ memory-safe jazyk a momentálně jsou v takovém stavu, že se ani nedostanou do C++26.
Myslím, že přímo s C je to těžké, protože pole je tam v podstatě jen ukazatel bez délky. Takže, pokud ho předáte do funkce, tak délku musíte předat jako další argument. Novější nízkoúrovňové C-like přidaly slice, což je dvojice ukazatel na první prvek a délka. A tam už jde snáze kontrolovat přístup mimo jeho hranice.
Můj názor je, že zásadní je styl programování. To, jak se C často učí, je IMO špatně a vede to k těm problémům. Když člověk začne používat jiný styl, tak si hodně problémů ušetří. Doporučuji How I program C od Eskila Steenberga + vyhnout se práci s jednotlivými hodnotami a pracovat spíše s jejich skupinami. Jednak je to lepší pro výkon programu a druhak je to snazší. Protože, když mám sto hodnot a každou z nich mám uvolnit/deinicializovat zvlášť, tak to spíše pokazím, než když celou skupinu uvolním/deinicializuji jedním nebo dvěma příkazy. To je také důvod, proč nové nízkoúrovňové jazyky kladou důraz na vlastní alokátory.
jako jsou attributy, ktere resi kompiler, treba
struct foo {
char c;
int x;
} __attribute__((packed));
tak by bylo neco jako pro ukazatel predstavujici pole, treba v ramci komentare, aby to nezasahovalo do kodu, ale specialni kompiler by to bral v potaz.
/* compiler: array size N */
char* arr=malloc(N*sizeof(char));