Muss ich in unsigned char umwandeln, bevor ich toupper(), tolower() usw. aufrufe?

Lesezeit: 8 Minuten

Muss ich in unsigned char umwandeln bevor ich toupper tolower
Baum mit Augen

Vor einiger Zeit schrieb jemand mit hohem Ansehen hier auf Stack Overflow in einem Kommentar, dass es notwendig sei, a zu casten char-Argument zu unsigned char vor dem Anruf std::toupper und std::tolower (und ähnliche Funktionen).

Andererseits erwähnt Bjarne Stroustrup die Notwendigkeit dazu nicht die Programmiersprache C++. Er benutzt nur toupper wie

string name = "Niels Stroustrup";

void m3() {
  string s = name.substr(6,10);  // s = "Stroustr up"
  name.replace(0,5,"nicholas");  // name becomes "nicholas Stroustrup"
  name[0] = toupper(name[0]);   // name becomes "Nicholas Stroustrup"
}

(Zitiert aus besagtem Buch, 4. Auflage.)

Die Referenz sagt, dass die Eingabe darstellbar sein muss als unsigned char. Für mich klingt das so, als würde es für alle gelten char seit char und unsigned char haben die gleiche Größe.

Ist diese Besetzung also unnötig oder war Stroustrup nachlässig?

Bearbeiten: Die libstdc++ Handbuch erwähnt, dass das Eingabezeichen aus dem stammen muss grundlegender Quellzeichensatz, wirft aber nicht. Ich denke, dies wird durch die Antwort von @Keith Thompson abgedeckt, sie alle haben eine positive Darstellung als signed char und unsigned char?

  • Es wäre schön, wenn Sie, wenn möglich, einen Link zu dem Kommentar posten könnten.

    – dyp

    16. Februar 2014 um 0:33 Uhr

  • Vielleicht finden Sie dies eine interessante Lektüre, einer der Top-Hits von [c] toupper cast.

    – WhozCraig

    16. Februar 2014 um 0:35 Uhr

  • @dyp stackoverflow.com/a/20182481/3002139

    – Baum mit Augen

    16. Februar 2014 um 0:39 Uhr

1646982015 115 Muss ich in unsigned char umwandeln bevor ich toupper tolower
Keith Thompson

Ja, das Argument dazu toupper umgewandelt werden muss unsigned char um das Risiko eines undefinierten Verhaltens zu vermeiden.

Die Typen char, signed charund unsigned char sind drei verschiedene Arten. char hat die gleiche Reichweite und Darstellung wie entweder signed char oder unsigned char. (Einfach char ist sehr häufig vorzeichenbehaftet und kann Werte im Bereich von -128 bis +127 darstellen.)

Die toupper Funktion dauert ein int Argument und gibt ein zurück int Ergebnis. Zitat der C-Norm, Abschnitt 7.4 Absatz 1:

In allen Fällen ist das Argument ein intderen Wert als darstellbar sein soll unsigned char oder soll gleich dem Wert des Makros sein EOF . Wenn das Argument einen anderen Wert hat, ist das Verhalten undefiniert.

(C++ enthält den größten Teil der C-Standardbibliothek und verschiebt seine Definition auf den C-Standard.)

Die [] Indizierungsoperator ein std::string gibt einen Verweis auf zurück char. Wenn klar char ein signierter Typ ist, und wenn der Wert von name[0] zufällig negativ ist, dann der Ausdruck

toupper(name[0])

hat undefiniertes Verhalten.

Die Sprache garantiert das, wenn auch schlicht char vorzeichenbehaftet ist, haben alle Mitglieder des grundlegenden Zeichensatzes nicht-negative Werte, so dass die Initialisierung gegeben ist

string name = "Niels Stroustrup";

das Programm riskiert kein undefiniertes Verhalten. Aber ja, im Allgemeinen a char Wert übergeben toupper (oder zu einer der in deklarierten Funktionen <cctype> / <ctype.h>) konvertiert werden muss unsigned charsodass die implizite Konvertierung in int ergibt keinen negativen Wert und verursacht kein undefiniertes Verhalten.

Die <ctype.h> Funktionen werden üblicherweise unter Verwendung einer Nachschlagetabelle implementiert. Etwas wie:

// assume plain char is signed
char c = -2;
c = toupper(c); // undefined behavior

kann außerhalb der Grenzen dieser Tabelle indizieren.

Beachten Sie, dass die Konvertierung in unsigned:

char c = -2;
c = toupper((unsigned)c); // undefined behavior

vermeidet das Problem nicht. Wenn int ist 32 Bit, die Konvertierung der char Wert -2 zu unsigned Erträge 4294967294. Diese wird dann implizit in konvertiert int (der Parametertyp), die wahrscheinlich Erträge -2.

toupper kann implementiert werden, damit es sich bei negativen Werten vernünftig verhält (alle Werte von akzeptieren CHAR_MIN zu UCHAR_MAX), aber es ist nicht erforderlich. Außerdem sind die Funktionen in <ctype.h> müssen ein Argument mit dem Wert akzeptieren EOFwas typisch ist -1.

Der C++-Standard nimmt Anpassungen an einigen Funktionen der C-Standardbibliothek vor. Zum Beispiel, strchr und mehrere andere Funktionen werden durch überladene Versionen ersetzt, die erzwingen const Richtigkeit. Für die in deklarierten Funktionen gibt es keine derartigen Anpassungen <cctype>.

  • Ich habe Ihnen +1 gegeben, weil die Antwort gut ist. Aber warum zitieren Sie in einer C++-Frage aus dem C-Standard?

    – Jonathan Mei

    3. Juni 2016 um 11:46 Uhr

  • @JonathanMee: Gute Frage. Das liegt daran, dass C++ den größten Teil der Standardbibliothek von C erbt und seine Definition auf den C-Standard überträgt.

    – Keith Thompson

    3. Juni 2016 um 15:27 Uhr

  • Die Umstellung zurück aus int zu char ist die Implementierung definiert, oder?

    – LF

    28. August 2019 um 4:27 Uhr

  • @KeithThompson Ich meine, nehme das an char ist vorzeichenbehaftet und hat den Wert -42. Dann wird umgerechnet unsigned char (213) und zu int (213). Jetzt ist nicht das Ergebnis von (char) 213 Umsetzung definiert?

    – LF

    28. August 2019 um 8:30 Uhr

  • @LF: Ja, guter Punkt! (Übrigens ist es 214, nicht 213.) Oder es kann ein implementierungsdefiniertes Signal auslösen, obwohl ich nicht glaube, dass eine Implementierung dies tut. In der Praxis dürfte es keine Probleme geben.

    – Keith Thompson

    28. August 2019 um 9:02 Uhr

1646982016 89 Muss ich in unsigned char umwandeln bevor ich toupper tolower
Sneftel

Die Referenz bezieht sich auf das Wertwesen darstellbar als ein unsigned charnicht dazu Sein ein unsigned char. Das heißt, das Verhalten ist undefiniert, wenn der tatsächliche Wert nicht zwischen 0 und liegt UCHAR_MAX (normalerweise 255). (Oder EOFwas im Grunde der Grund ist, warum es dauert int anstelle einer char.)

  • Als Parameter von toupper ist ein intdenke ich negativ char Werte könnten UB verursachen. Jede Konvertierung von int zu unsigned char geschieht intern in der Funktion.

    – dyp

    16. Februar 2014 um 0:35 Uhr


  • Niemand hat das gesagt unsigned char kann keine Werte größer als 255 darstellen.

    – Kerrek SB

    9. April 2015 um 21:40 Uhr

  • @dyp “Jede Konvertierung von int zu unsigned char passiert intern in der Funktion.” –> Nicht ganz so wie das umrechnen kann EOF bis 255. Nach dem fertigwerden mit EOFUmwandlung in unsigned char wäre vernünftig, aber dieses Verhalten ist nicht spezifiziert.

    – chux – Wiedereinsetzung von Monica

    22. Februar 2018 um 20:40 Uhr

1646982017 891 Muss ich in unsigned char umwandeln bevor ich toupper tolower
Max Lybbert

In C, toupper (und viele andere Funktionen) übernehmen ints, obwohl Sie erwarten würden, dass sie nehmen charS. Zusätzlich, char ist auf einigen Plattformen signiert und auf anderen unsigniert.

Der Ratschlag zu werfen unsigned char vor dem Anruf toupper ist richtig für C. Ich glaube nicht, dass es in C++ benötigt wird, vorausgesetzt, Sie übergeben es an int das ist in Reichweite. Ich kann nichts Bestimmtes darüber finden, ob es in C++ benötigt wird.

Wenn Sie das Problem umgehen möchten, verwenden Sie die toupper definiert in <locale>. Es ist eine Vorlage und akzeptiert jeden akzeptablen Zeichentyp. Sie müssen es auch bestehen a std::locale. Wenn Sie keine Ahnung haben, welches Gebietsschema Sie wählen sollen, verwenden Sie std::locale("")das das bevorzugte Gebietsschema des Benutzers sein soll:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <locale>
#include <string>

int main()
{
    std::string name("Bjarne Stroustrup");
    std::string uppercase;

    std::locale loc("");

    std::transform(name.begin(), name.end(), std::back_inserter(uppercase),
                   [&loc](char c) { return std::toupper(c, loc); });

    std::cout << name << '\n' << uppercase << '\n';
    return 0;
}

  • Ja, es ist richtig für C. Warum, glauben Sie, gilt das Gleiche nicht für C++?

    – Keith Thompson

    16. Februar 2014 um 1:11 Uhr

  • Es wird in C auch nicht benötigt, wenn Sie es an übergeben int an erster Stelle. Es ist benötigt, wenn Sie eine bestehen char in entweder.

    – WhozCraig

    16. Februar 2014 um 1:11 Uhr

  • @KeithThompson Ich habe den Standard nicht überprüft, aber ehrlich gesagt glaube ich nicht, dass die Besetzung für C ++ benötigt wird, weil ich den Ratschlag zur Besetzung in C-Projekten immer nur gesehen habe. Es ist möglich, dass ich einfach nicht die richtigen Artikel gelesen habe, aber ich finde es interessant, dass ich noch nie einen C++-Experten gesehen habe, der die Notwendigkeit einer Umwandlung erwähnt hat, während ich gesehen habe, dass C-Experten dies erwähnt haben.

    – Max Lybbert

    16. Februar 2014 um 1:55 Uhr

  • C++ enthält den größten Teil der C-Standardbibliothek als Referenz (C++11 bezieht sich auf die C99-Bibliothek, aber <ctype.h> hat sich nicht viel geändert, wenn überhaupt, von C90 zu C99 zu C11). Es gibt einige Fälle, in denen C++ Änderungen an der C-Standardbibliothek vornimmt, aber ich sehe keine Erwähnung solcher Änderungen <ctype.h>. Ich denke, den C++-Experten fehlt einfach etwas. (toupper(c) ist “sicher”, wenn bekannt ist, dass sein Argument im Basiszeichensatz enthalten ist.)

    – Keith Thompson

    16. Februar 2014 um 3:37 Uhr

1646982017 966 Muss ich in unsigned char umwandeln bevor ich toupper tolower
Benutzer3277268

Leider war Stroustrup nachlässig 🙁
Und ja, lateinische Buchstabencodes sollten nicht negativ sein (und es ist kein Cast erforderlich) …
Einige Implementierungen funktionieren korrekt ohne Umwandlung in unsigned char …
Erfahrungsgemäß kann es einige Stunden dauern, die Ursache für einen Segfault eines solchen Toppers zu finden (wenn bekannt ist, dass ein Segfault vorhanden ist) …
Und es gibt auch isupper, islower etc

Anstatt das Argument als unsigned char umzuwandeln, können Sie die Funktion umwandeln. Sie müssen einbeziehen funktionell Header. Hier ist ein Beispielcode:

#include <string>
#include <algorithm>
#include <functional>
#include <locale>
#include <iostream>

int main()
{
    typedef unsigned char BYTE; // just in case

    std::string name("Daniel Brühl"); // used this name for its non-ascii character!

    std::transform(name.begin(), name.end(), name.begin(),
            (std::function<int(BYTE)>)::toupper);

    std::cout << "uppercase name: " << name << '\n';
    return 0;
}

Die Ausgabe ist:

uppercase name: DANIEL BRüHL

Wie erwartet hat toupper keine Auswirkungen auf Nicht-ASCII-Zeichen. Aber dieses Casting ist vorteilhaft, um unerwartetes Verhalten zu vermeiden.

  • Das scheint zu funktionieren, aber es scheint ziemlich verworren. Was ist besser, als das Argument zu werfen?

    – Keith Thompson

    23. Januar 2016 um 22:38 Uhr

  • @KeithThompson Beachten Sie, dass die Funktion toupper wird als Lambda-Ausdruck übergeben. In diesem Fall kann es also besser sein, die Funktion zu casten.

    – Polfosol ఠ_ఠ

    24. Januar 2016 um 12:54 Uhr

  • “Wie erwartet hat toupper keine Auswirkung auf Nicht-ASCII-Zeichen” –> sicherlich a Gebietsschema Ausgabe.

    – chux – Wiedereinsetzung von Monica

    22. Februar 2018 um 20:45 Uhr

  • Das scheint zu funktionieren, aber es scheint ziemlich verworren. Was ist besser, als das Argument zu werfen?

    – Keith Thompson

    23. Januar 2016 um 22:38 Uhr

  • @KeithThompson Beachten Sie, dass die Funktion toupper wird als Lambda-Ausdruck übergeben. In diesem Fall kann es also besser sein, die Funktion zu casten.

    – Polfosol ఠ_ఠ

    24. Januar 2016 um 12:54 Uhr

  • “Wie erwartet hat toupper keine Auswirkung auf Nicht-ASCII-Zeichen” –> sicherlich a Gebietsschema Ausgabe.

    – chux – Wiedereinsetzung von Monica

    22. Februar 2018 um 20:45 Uhr

990150cookie-checkMuss ich in unsigned char umwandeln, bevor ich toupper(), tolower() usw. aufrufe?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy