C / C++ Best Practices mit signierten / unsignierten Ints und Funktionsaufrufen
Lesezeit: 6 Minuten
JamIC
Ich stelle diese Frage für zwei verschiedene Sprachen: C und C++.
Was ist die beste Vorgehensweise beim Aufrufen von Funktionen, die eine entgegengesetzte Ganzzahlerwartung zu dem haben, was wir in unserem Code benötigen?
_BitScanForward erwartet DWORD (uint32) Parameter. Die Variable input ist vom Typ int16 und ich muss das Ergebnis verarbeiten _depth als int32 in meinem Code.
Muss ich mich um das Casting kümmern? input wie gezeigt? Ich weiß, dass der Complier es tun wird wahrscheinlich tun Sie es für mich, aber was ist die beste Vorgehensweise?
Ist es akzeptabel zu erklären _depth als int32 und vermeiden Sie daher, es anschließend wie gezeigt umzuwandeln?
HINWEIS:
Mein Kommentar zum Compiler basiert auf Erfahrung. Ich habe Code geschrieben, der ohne Warnungen in VS kompiliert wurde, aber bei der Ausführung abstürzte. Es stellte sich heraus, dass ich eine Funktion mit einer falschen Breite int aufgerufen habe. Also überlasse ich dieses Thema nicht mehr dem Compiler.
BEARBEITEN:
Die Antworten sind hilfreich, danke. Lassen Sie mich meine Frage präzisieren. Wenn es keine Breitenprobleme gibt, dh die Funktion erwartet kein schmaleres Int als das, was übergeben wird (wird offensichtlich fehlschlagen), ist es dann in Ordnung, sich auf den Compiler zu verlassen, um Zeichen- und Breitenunterschiede zu handhaben?
„Kann ich _depth als int32 deklarieren“ Nun, hält dich irgendetwas davon ab?
– Daniel Daranas
26. September 2016 um 11:40 Uhr
“Wahrscheinlich” tun es für Sie?
– Leichtigkeitsrennen im Orbit
26. September 2016 um 11:43 Uhr
Welche Sprache verwenden Sie? C oder C++? Dies sind zwei unterschiedliche Sprachen. Wähle eins!
– Leichtigkeitsrennen im Orbit
26. September 2016 um 11:44 Uhr
@DanielDaranas Definition von “could” = “Wird verwendet, um Fähigkeit oder Erlaubnis anzuzeigen”. Erlaubnis wie in Annehmbarkeit. Aber ich habe meine Frage nur für dich geändert 😉
– IamIC
26. September 2016 um 11:48 Uhr
Bitte fragen Sie nach eines Sprache auf einmal. Wenn die Antwort für jede der von Ihnen genannten Sprachen unterschiedlich ist, macht das alles durcheinander! Sicherlich verwenden Sie tatsächlich das eine oder andere, also nennen Sie einfach das eine.
– Leichtigkeitsrennen im Orbit
26. September 2016 um 13:20 Uhr
Ich würde dringend empfehlen, diese Funktion in einer benutzerdefinierten Wrapper-Funktion zu verstecken, die mit Ihrer bevorzugten API übereinstimmt (und innerhalb dieser Funktion das richtige explizite Casting durchzuführen). Im Fall der Verwendung von Compiler-spezifischen Funktionen hat dies den zusätzlichen Vorteil, dass es viel einfacher ist, es auf verschiedene Compiler zu portieren (sollten Sie das jemals tun wollen), indem Sie einfach diese Wrapper-Funktion neu implementieren.
Dies ist eine gute Lösung für den Bibliotheksbetrieb. Großartige Idee!
– IamIC
26. September 2016 um 12:03 Uhr
Es ist sehr wichtig, eine explizite Umwandlung zu schreiben, wenn Sie von einem Integer-Typ ausgehen, der schmaler als ist int zu jedem ganzzahligen Typ, der die gleiche Breite oder breiter als ist int. Wenn Sie dies nicht tun, wird der Compiler es tun Erste Konvertieren Sie den Wert in int, wegen der “Integer Promotion”-Regeln und dann zum Zieltyp. Das ist fast immer falsch, und wir würden die Sprache nicht auf diese Weise entwerfen, wenn wir heute bei Null anfangen würden, aber aus Kompatibilitätsgründen bleiben wir dabei.
Vom System bereitgestellte Typedefs wie uint16_t, uint32_t, WORDund DWORD kann schmaler, breiter oder gleich groß sein wie int; In C++ können Sie Vorlagen verwenden, um es herauszufinden, aber in C können Sie das nicht. Daher möchten Sie möglicherweise explizite Umwandlungen für schreiben irgendein Konvertierung mit diesen.
Bei der Entscheidung, dass kurze vorzeichenlose Werte zu vorzeichenbehafteten hochgestuft werden sollten, stellten die Autoren des C-Standards fest, dass sich die Mehrheit der damals aktuellen Compiler für Konstrukte wie zuverlässig arithmetisch korrekt verhalten würden unsigned mul(unsigned short x, unsigned short y) { return x*y; } auch für Ergebnisse im Bereich INT_MAX+1 bis UINT_MAX. Aus irgendeinem Grund haben die Betreuer von gcc jedoch entschieden, dass es nützlicher ist, den “Optimierer” Code generieren zu lassen, der nur für Werte bis zu INT_MAX zuverlässig ist.
– Superkatze
26. September 2016 um 16:48 Uhr
@IamIC Es ist immer noch die beste Vorgehensweise in Ihrem Fall, weil uint32 und int möglicherweise nicht immer gleich groß; zum Beispiel, wenn Sie jemals den Code in eine andere Umgebung portieren. Betreff mallocleider ist der einzig zuverlässige Weg, diese Operation auszuführen, etwas komplizierter, als dass er in einen einzigen Ausdruck passt, siehe lteo.net/blog/2014/10/28/… (Lies das ganze Sache).
– zol
26. September 2016 um 17:37 Uhr
@IamIC: Die einzige Möglichkeit, wie dieser Ausdruck Probleme verursachen würde, wäre, wenn das Produkt den Maximalwert für size_t überschreitet. Wenn “int” 16 Bits, aber “size_t” 8 Bits und “sizeof(MyType)” 200 und “i” 240 wären, dann würde die Multiplikation ein undefiniertes Verhalten ergeben, anstatt 0xC8u * 0xF0u (dh 0xBB80) zu berechnen und Übergeben von “malloc” einen Wert von 0x80 (128). Andererseits wäre das Hinzufügen von Umwandlungen, damit der Code malloc() einen Wert von 128 übergibt, anstatt UB in der Multiplikation aufzurufen, wahrscheinlich nicht sehr nützlich.
– Superkatze
26. September 2016 um 17:54 Uhr
@IamIC Meiner Meinung nach ist das Problem, das Supercat anführt, nicht annähernd so wichtig wie das Problem, Werte vorzeichenerweitert zu bekommen, wo Sie erwartet haben, dass sie nullerweitert sind, oder umgekehrt.
– zol
26. September 2016 um 18:19 Uhr
@zwol: Ich würde sagen, dass die Tatsache, dass sich Code unter seltenen, aber unvorhersehbaren Umständen manchmal bizarr verhält, ein größeres Problem ist als Code, der sich deterministisch und logisch verhält, aber nicht den Anforderungen der Anwendung entspricht. Die interessanten “Optimierungen”, die gcc an der obigen Funktion vornimmt, erzeugen nicht nur unerwartete Werte – wenn zB gcc erkennt, dass ein Argument immer 65535 sein wird, kann es daraus schließen, dass das andere Argument 32768 nicht überschreiten kann, und anderen Code weglassen, der dies tun würde nur relevant, wenn eine solche Bedingung eintritt.
– Superkatze
26. September 2016 um 18:53 Uhr
Heut
Nun, es hängt irgendwie von Ihrer Verwendung usw. ab:
Wenn ich den benötigten Typ verwenden kann, verwende ich einfach den Typ.
Wenn nicht: Ihr Compiler sollte Sie in den Fällen warnen, in denen Sie Datentypen implizit konvertieren, was zu Über-/Unterläufen führen kann. Also habe ich diese Warnungen normalerweise aktiviert und ändere die implizite Konvertierung in explizite.
Da habe ich 2 verschiedene Ansätze:
Wenn ich mir zu 100% sicher bin, dass ich die Grenzen zwischen signed/unsigned int niemals über-/unterschreite, verwende ich static_cast. (normalerweise für die Konvertierung verschiedener APIs. Wie size() gibt int vs size_t zurück).
Wenn ich mir nicht sicher bin oder es möglich ist, bin ich jenseits der Grenzen, die ich verwende boost::numeric_cast. Dies löst eine Ausnahme aus, wenn Sie über Grenzen hinaus wirken, und zeigt somit an, wann dies geschieht.
Der Ansatz mit den Ausnahmen hält sich an die Praxis, hart zu versagen/abzustürzen/zu beenden, wenn etwas schief geht, anstatt mit beschädigten Daten fortzufahren und dann woanders abzustürzen oder andere Dinge mit undefinierten Daten zu tun.
Zuerst macht Ihr Compiler die Umwandlungen implizit und gibt Ihnen eine Warnung auf jeder sinnvollen Warnstufe.
Beide Umwandlungen, die Sie durchführen, sind Umwandlungen, bei denen der Compiler (oder Ihre Mitarbeiter) nicht einfach entscheiden können, ob sie korrekt sind, daher ist eine explizite Umwandlung oder explizite Konvertierung mit einem Begrenzungstest die beste Vorgehensweise. Welche Sie wählen, hängt von Ihrer Kenntnis der Daten ab. Am sichersten ist es, die Randbedingungen zu prüfen. Der billigste Weg ist einfach zu casten (in C++ verwenden Sie bitte static_cast und keine Casts im C-Stil).
13829000cookie-checkC / C++ Best Practices mit signierten / unsignierten Ints und Funktionsaufrufenyes
„Kann ich _depth als int32 deklarieren“ Nun, hält dich irgendetwas davon ab?
– Daniel Daranas
26. September 2016 um 11:40 Uhr
“Wahrscheinlich” tun es für Sie?
– Leichtigkeitsrennen im Orbit
26. September 2016 um 11:43 Uhr
Welche Sprache verwenden Sie? C oder C++? Dies sind zwei unterschiedliche Sprachen. Wähle eins!
– Leichtigkeitsrennen im Orbit
26. September 2016 um 11:44 Uhr
@DanielDaranas Definition von “could” = “Wird verwendet, um Fähigkeit oder Erlaubnis anzuzeigen”. Erlaubnis wie in Annehmbarkeit. Aber ich habe meine Frage nur für dich geändert 😉
– IamIC
26. September 2016 um 11:48 Uhr
Bitte fragen Sie nach eines Sprache auf einmal. Wenn die Antwort für jede der von Ihnen genannten Sprachen unterschiedlich ist, macht das alles durcheinander! Sicherlich verwenden Sie tatsächlich das eine oder andere, also nennen Sie einfach das eine.
– Leichtigkeitsrennen im Orbit
26. September 2016 um 13:20 Uhr