wie du schon sagst ist es nur ne konvention. prinzipiell kannst du die dateien natürlich benennen und auch reinschreiben was du willst (genauso wie man das komplette c-programm auch in ne einzelne zeile schreiben kann, oder das ganze programm mit präprozessordirektiven formulieren kann), solange es dann richtig übersetzt wird.
genaugenommen ist in der sprachdefinition von c/c++ nicht mal festgelegt, das der quelltext als text in einer datei stehen muss (mit nem entsprechenden editor/system könnte das programm z.b. direkt in einer baumstruktur gespeichert werden)

von einer headerdatei (*.h) nimmt man üblicherweise an, dass sie dinge enthält, die keinen code und daten erzeugen, und das man sie deshalb in mehrerer dateien oder auch mehrfach einbinden kann, ohne das es probleme gibt.

wenn du nun also trotzdem code und daten reinschreibst, und ein anderer verwendet die datei (oder schaut sich dein programm an) dann wird er eben wahrscheinlich unzutreffende annahmen über deine datei machen, was zu problemen führen kann.

es gibt wohl ne menge verschiedene gründe für diese konvention und vermutlich kenne ich nicht mal die hälfte davon.

in c++ ists üblich template-code in die *.h zu schreiben. teilweise muss man es sogar machen. aber templates sind auch etwas anderes als normaler c/c++ sourcecode.
bei templates wird erst code erzeugt, wenn das template verwendet wird.
wenn du jedoch ne normale funktion in ne header schreibst und diese dann mehrfach einbindest, dann wird mehrfach code für diese funktion erzeugt. normalerweise will man das nicht.

du kannst die datei doch auch einfach in *.c umbenennen und mit include einbinden. dann hast du praktisch nichts geändert, aber jeder der sich das anschaut, weiß sofort, das in der datei code und daten erzeugt werden.

ansonsten bestätigen ausnahmen die regel