Grüß euch
Mich würde interessieren wie ihr die Parameterübergabe an
Funktionen/Klassen/etc. in Sprachen handhabt, die über verschiedene
fixed-width Typen verfügen.
Also also Beispiel bei C/C++ in etwa uint8_t, int16_t, usw. usf.
Nehmen wir an es gibt eine Funktion die irgendeinen jener Typen annimmt
und damit irgendwas anstellt.
1
voidf(Tv);
Wie wählt ihr den Übergabe-Parameter aus wenn
- die Funktion ein Byte irgendwohin aussendet
- die Funktion einen 16bit Timer setzt
- die Funktion irgendwas berechnet, der Übergabewert aber etwa nur bis
16bit skalieren darf
Interessanterweise ertappe ich mich selbst dabei, dass ich vom ersten
bis zum letzten Beispiel immer weniger strikt mit dem Typen bin. Sprich
bei einer Funktion die ein Byte sendet würde ich ohne zögern ein
einzelnes Byte übergeben während ich beim Timer wohl schon nicht mehr so
sicher wäre...
Die Frage ist, mit welcher Argumentation? Wäre es nicht besser überall
explizit zu sein und auch beim Timer lediglich 16bit Werte zuzulassen?
Wie handhabt ihr sowas?
Ich finde, exakte Größen sollten nur dort eingesetzt werden, wo auch
zwingend eine benötigt wird, also z.B. bei Hardware-Registern oder einem
binären Protokoll.
Nur um Wertebereiche einzuschränken, würde ich sie nicht nehmen, schon
alleine, weil das gar nicht durchgängig klappt. Nehmen wir mal an, dein
Timer wird im Code fix auf einen Overflow-Wert von 10000 gestellt, um
mit 10 Mhz betrieben zu werden und immer exakt einmal pro Millisekunde
überzulaufen. Es gibt aber in der Regel keinen Typ, der exakt bis 9999
geht.
Rolf M. schrieb:> Ich finde, exakte Größen sollten nur dort eingesetzt werden, wo auch> zwingend eine benötigt wird, also z.B. bei Hardware-Registern oder einem> binären Protokoll.> Nur um Wertebereiche einzuschränken, würde ich sie nicht nehmen, schon> alleine, weil das gar nicht durchgängig klappt. Nehmen wir mal an, dein> Timer wird im Code fix auf einen Overflow-Wert von 10000 gestellt, um> mit 10 Mhz betrieben zu werden und immer exakt einmal pro Millisekunde> überzulaufen. Es gibt aber in der Regel keinen Typ, der exakt bis 9999> geht.
Ja des letzte Beispiel gefällt mir eigentlich auch nicht mehr und war
unüberlegt. Aber nehmen wir ein anderes, vielleicht ein EEPROM mit 16bit
Adressraum oder so.
1
eeprom_write(Tadress,...)
Das wäre dann ähnlich zum Timer. Physikalisch wird exakt jenes EEPROM
immer nur 16bit Adressen schicken, aber sollte sowas Teil der Signatur
sein oder doch nur Teil der Doku?
Der Vorteil eines uint16_t als Übergabewert besteht daran, dass jeder
unüberlegte Aufruf sofort ein -Wconversion Warning erzeugt.
Dummerweise besteht der Nachteil eines uint16_t als Übergabewert darin,
dass jeder Aufruf sofort ein -Wconversion Warning erzeugt. ;)
1
uint8_tb;
2
uint16_tadr;
3
eeprom_write(adr,b);
4
eeprom_write(static_cast<uint16_t>(adr+1u),b);
Exakte Größen sorgen recht schnell dafür dass der eigene Code mit
static_casts gespickt ist...
Vincent H. schrieb:> Aber nehmen wir ein anderes, vielleicht ein EEPROM mit 16bit> Adressraum oder so.> eeprom_write(T adress, ...)>> Das wäre dann ähnlich zum Timer. Physikalisch wird exakt jenes EEPROM> immer nur 16bit Adressen schicken, aber sollte sowas Teil der Signatur> sein oder doch nur Teil der Doku?
Da würde ich size_t nehmen. Fehler würde ich zur Runtime loggen und
zurückgeben. Und dann eventuell noch eine get_eeprom_size Funktion, je
nachdem, wofür man das braucht. Bzw. bei Addressen würde ich wohl sogar
void Pointer nehmen.
So wie ich mich kenne würde ich das vermutlich noch weiter abstrahieren,
und ein memory Interface mit read write size Funktionen anlegen und im
eeprom Modul dieses implementieren. Also sowas: (ungetestet)
memory.h
Vermutlich würde ich mir dann noch eine memory registry/factory
basierend auf allgemeinen registry/factory funktionen bauen und die
statischen Memories sich darin selbst registrieren lassen und solches
zeug. Und bla_memory und bla_location würde ich vermutlich auslagern und
mit einem anderen Programm generieren lassen, usw. Das heist, falls ich
nicht gleich ein ganzes Dateisystem mit ähnlicher Abstraktion einbaue...
"fs_mount("fat", ns_memory_registry_get("EEPROM","internal1"));". Und
eventuell würde ich dann noch einen memory manager bauen, der Daten auf
die Memories verteilt abhängig von verfügbarem Platz, Schreibrate,
Geschwindigkeit, verbleibende Zyklen, dann muss ich auch nichtmehr
angeben welchen Memory... Ups, ich glaube schreibe schon wieder
versehentlich ein OS...
(Falls sich jemand wundert, warum ich das in C und nicht in C++ mache:
Wenn ich das in C schon so Overengineere, stellt euch mal vor, was ich
in C++ anstellen würde...)
Vincent H. schrieb:> dass der eigene Code mit static_casts gespickt ist
In C kommt man immerhin noch mit weniger lang auszuschreibenden und nur
einer Art des Casts zurecht.
Vincent H. schrieb:> Ja des letzte Beispiel gefällt mir eigentlich auch nicht mehr und war> unüberlegt. Aber nehmen wir ein anderes, vielleicht ein EEPROM mit 16bit> Adressraum oder so.>>
1
>eeprom_write(Tadress,...)
2
>
Die Frage ist doch, ob Adressen vorzeichenlose, Ganzzahlen sind. Das
sind sie m.E. nicht, denn bspw. eine Multiplikation oder Division oder
Addition zweier Adressen ist nicht sinnvoll. Das Addieren einer Adresse
mit einem Offset schon.
Daher schreibe ich für so etwas einen eigenen DT.
Genau aus diesem Grunde hat man ja (endlich) in der C++-stdlib auch den
DT std::byte eingefügt. Denn ein Byte ist etwas anderes als ein uint8_t.
>
1
>uint8_tb;
2
>uint16_tadr;
3
>eeprom_write(adr,b);
4
>eeprom_write(static_cast<uint16_t>(adr+1u),b);
5
>
>> Exakte Größen sorgen recht schnell dafür dass der eigene Code mit> static_casts gespickt ist...
Nicht, wenn Du Dir domänenspezifische DT nach dem obigen Muster baust.
Vincent H. schrieb:> Ja des letzte Beispiel gefällt mir eigentlich auch nicht mehr und war> unüberlegt. Aber nehmen wir ein anderes, vielleicht ein EEPROM mit 16bit> Adressraum oder so.
Und übermorgen hast du ein EEPROM mit 18 Bit Adressraum.
Dann musst du wieder neue Funktionen schreiben.
Jede Einschränkung schränkt ein.
Wilhelm M. schrieb:> Vincent H. schrieb:>>> Ja des letzte Beispiel gefällt mir eigentlich auch nicht mehr und war>> unüberlegt. Aber nehmen wir ein anderes, vielleicht ein EEPROM mit 16bit>> Adressraum oder so.>>>>
1
>>eeprom_write(Tadress,...)
2
>>
>> Die Frage ist doch, ob Adressen vorzeichenlose, Ganzzahlen sind. Das> sind sie m.E. nicht, denn bspw. eine Multiplikation oder Division oder> Addition zweier Adressen ist nicht sinnvoll. Das Addieren einer Adresse> mit einem Offset schon.>> Daher schreibe ich für so etwas einen eigenen DT.
Schreibst du dann für alles einen Wrapper?
Ich mach das aktuell eher an Stellen wo Funktionsargumente sonst
unübersichtlich wären (int, int, int...)
Aber ja, mit einem
Vincent H. schrieb:> Wilhelm M. schrieb:>> Vincent H. schrieb:>>>>> Ja des letzte Beispiel gefällt mir eigentlich auch nicht mehr und war>>> unüberlegt. Aber nehmen wir ein anderes, vielleicht ein EEPROM mit 16bit>>> Adressraum oder so.>>>>>>
1
>>>eeprom_write(Tadress,...)
2
>>>
>>>> Die Frage ist doch, ob Adressen vorzeichenlose, Ganzzahlen sind. Das>> sind sie m.E. nicht, denn bspw. eine Multiplikation oder Division oder>> Addition zweier Adressen ist nicht sinnvoll. Das Addieren einer Adresse>> mit einem Offset schon.>>>> Daher schreibe ich für so etwas einen eigenen DT.>> Schreibst du dann für alles einen Wrapper?
Ja.
Man hat viele Vorteile zur Compilezeit und keine Nachteile zur Laufzeit.
> Ich mach das aktuell eher an Stellen wo Funktionsargumente sonst> unübersichtlich wären (int, int, int...)
Na, da sowieso. Solche mehrstelligen Funktionen gehen gar nicht.
So etws nennt sich "String Data Types". Gerade das ist ja ein Vorteil
von C++.
Scott Meyers sagte schon: eine Schnittstelle soll leicht richtig und
schwer falsch zu benutzen sein.
Die primitiven DT sind ja nur die Grundbausteine einer Sprache. Ohne das
geht es (fast) nicht. D.h. aber nicht, dass alles ein uint8_t ist ;-) Im
Gegenteil!
> Aber ja, mit einem>