Sind C-Strings immer nullterminiert oder hängt dies von der Plattform ab?

13

Im Moment arbeite ich mit eingebetteten Systemen und finde heraus, wie Strings auf einem Mikroprozessor ohne Betriebssystem implementiert werden können. Bisher verwende ich nur die Idee, Zeichenzeiger mit NULL-Terminierung zu verwenden und sie als Zeichenfolgen zu behandeln, wobei NULL das Ende bedeutet. Ich weiß, dass dies ziemlich häufig ist, aber können Sie sich immer darauf verlassen, dass dies der Fall ist?

Der Grund, den ich frage, ist, dass ich darüber nachgedacht habe, irgendwann ein Echtzeitbetriebssystem zu verwenden, und ich möchte so viel wie möglich meinen aktuellen Code wiederverwenden. Kann ich also für die verschiedenen Auswahlmöglichkeiten, die es gibt, ziemlich genau erwarten, dass die Saiten gleich funktionieren?

Lassen Sie mich jedoch genauer auf meinen Fall eingehen. Ich implementiere ein System, das Befehle über eine serielle Schnittstelle entgegennimmt und verarbeitet. Kann ich meinen Befehlsverarbeitungscode beibehalten und dann erwarten, dass die auf dem RTOS (das die Befehle enthält) erstellten Zeichenfolgenobjekte alle mit NULL beendet werden? Oder wäre es je nach Betriebssystem anders?

Aktualisieren

Nachdem mir geraten wurde, diese Frage zu prüfen, habe ich festgestellt, dass sie nicht genau das beantwortet, was ich stelle. Die Frage selbst ist, ob die Länge eines Strings immer übergeben werden sollte, was völlig anders ist als das, was ich frage, und obwohl einige der Antworten nützliche Informationen enthielten, sind sie nicht genau das, wonach ich suche. Die Antworten scheint es Gründe zu geben , warum oder warum nicht eine Zeichenfolge mit einem Null - Zeichen zu beenden. Der Unterschied zu dem, was ich frage, besteht darin, ob ich mehr oder weniger erwarten kann, dass die angeborenen Zeichenfolgen verschiedener Plattformen ihre eigenen Zeichenfolgen mit null beenden, ohne dass ich jede einzelne Plattform ausprobieren muss, wenn dies sinnvoll ist.

Schnüffeln
quelle
3
Ich habe C schon lange nicht mehr verwendet, aber ich kann mir keine Zeit vorstellen, in der ich auf eine Implementierung gestoßen bin, die keine NULL-terminierten Zeichenfolgen verwendet hat. Es ist Teil von Standard C, wenn ich mich richtig erinnere (wie gesagt, es ist eine Weile her ...)
MetalMikester
1
Ich bin kein Spezialist für C, aber soweit ich weiß, sind alle Zeichenfolgen in C Arrays von char, nullterminiert. Sie können zwar Ihren eigenen Zeichenfolgentyp erstellen, müssen jedoch alle Funktionen zur Zeichenfolgenmanipulation selbst implementieren.
Machado
1
@MetalMikester Sie glauben, dass diese Informationen in der Standard-C-Spezifikation enthalten sind?
Snoop
3
@ Snoopy Höchstwahrscheinlich ja. Aber wenn es um Zeichenfolgen in C geht, handelt es sich nur um eine Reihe von Zeichen, die mit NULL enden, und das ist es, es sei denn, Sie verwenden eine nicht standardmäßige Zeichenfolgenbibliothek, aber darüber sprechen wir hier sowieso nicht. Ich bezweifle, dass Sie eine Plattform finden werden, die dies nicht respektiert, insbesondere wenn eine der Stärken von C die Portabilität ist.
MetalMikester

Antworten:

42

Die Dinge, die als "C-Strings" bezeichnet werden, werden auf jeder Plattform mit Null terminiert. Auf diese Weise bestimmen die Standardfunktionen der C-Bibliothek das Ende einer Zeichenfolge.

In der C-Sprache hindert Sie nichts daran, ein Array von Zeichen zu haben, das nicht mit einer Null endet. Sie müssen jedoch eine andere Methode verwenden, um zu vermeiden, dass das Ende einer Zeichenfolge abläuft.

Simon B.
quelle
4
nur um hinzuzufügen; Normalerweise haben Sie irgendwo eine Ganzzahl, um die Länge der Zeichenfolge zu verfolgen, und dann erhalten Sie eine benutzerdefinierte Datenstruktur, um dies richtig zu machen, so etwas wie die QString-Klasse in Qt
Rudolf Olah
8
Beispiel: Ich arbeite mit einem C-Programm, das mindestens fünf verschiedene Zeichenfolgenformate verwendet: nullterminierte charArrays, charArrays mit der im ersten Byte codierten Länge (allgemein als "Pascal-Zeichenfolgen" bezeichnet), wchar_tbasierte Versionen beider oben und charArrays, die beide Methoden kombinieren: Länge, die im ersten Byte codiert ist, und ein Nullzeichen, das die Zeichenfolge beendet.
Mark
4
@ Mark Interfacing mit vielen Komponenten / Anwendungen von Drittanbietern oder einem alten Code-Durcheinander?
Dan spielt am Feuer
2
@ DanNeely, alle oben genannten. Pascal-Zeichenfolgen für die Schnittstelle mit klassischem MacOS, C-Zeichenfolgen für den internen Gebrauch und Windows, breite Zeichenfolgen für das Hinzufügen von Unicode-Unterstützung und Bastard-Zeichenfolgen, weil jemand versucht hat, klug zu sein und eine Zeichenfolge zu erstellen, die gleichzeitig mit MacOS und Windows kompatibel ist.
Mark
1
@Mark ... und natürlich ist niemand bereit, Geld auszugeben, um die technischen Schulden zu begleichen, da das klassische MacOS längst tot ist und die Bastard-Saiten jedes Mal, wenn sie berührt werden müssen, ein doppelter Clusterfrak sind. Mein Mitgefühl.
Dan spielt am Feuer
22

Die Bestimmung des Abschlusszeichens liegt beim Compiler für Literale und der Implementierung der Standardbibliothek für Zeichenfolgen im Allgemeinen. Es wird nicht vom Betriebssystem bestimmt.

Die Konvention der NULKündigung geht zurück auf C vor dem Standard, und in mehr als 30 Jahren kann ich nicht sagen, dass ich auf eine Umgebung gestoßen bin, die etwas anderes tut. Dieses Verhalten wurde in C89 kodifiziert und ist weiterhin Teil des C-Sprachstandards (Link zu einem Entwurf von C99):

  • In Abschnitt 6.4.5 wird die Bühne für abgeschlossene NULZeichenfolgen festgelegt, indem verlangt wird, dass a NULan Zeichenfolgenliterale angehängt wird.
  • Abschnitt 7.1.1 bringt dies zu den Funktionen in der Standardbibliothek, indem eine Zeichenfolge als "zusammenhängende Folge von Zeichen definiert wird, die mit dem ersten Nullzeichen abgeschlossen sind und dieses enthalten".

Es gibt keinen Grund, warum jemand keine Funktionen schreiben könnte, die Zeichenfolgen verarbeiten, die von einem anderen Zeichen beendet werden, aber es gibt in den meisten Fällen auch keinen Grund, sich gegen den etablierten Standard zu sträuben, es sei denn, Ihr Ziel ist es, Programmierern Passungen zu geben. :-)

Blrfl
quelle
2
Ein Grund wäre, zu vermeiden, dass das Ende derselben Zeichenfolge immer wieder gefunden werden muss.
Paŭlo Ebermann
@ PaŭloEbermann Richtig. Auf Kosten von zwei Werten anstelle von einem. Das ist etwas lästig, wenn Sie nur ein String-Literal wie in übergeben printf("string: \"%s\"\n", "my cool string"). Die einzige Möglichkeit, in diesem Fall vier Parameter zu übergeben (abgesehen von einer Art Abschlussbyte), besteht darin, eine Zeichenfolge so zu definieren, dass sie std::stringin C ++ ähnlich ist und ihre eigenen Probleme und Einschränkungen aufweist.
cmaster - wieder einsetzen Monica
1
In Abschnitt 6.4.5 muss ein Zeichenfolgenliteral nicht mit einem Nullzeichen abgeschlossen werden. Es wird ausdrücklich darauf hingewiesen, dass " ein Zeichenfolgenliteral keine Zeichenfolge sein muss (siehe 7.1.1), da ein Nullzeichen durch eine \ 0-Escape-Sequenz darin eingebettet sein kann. "
bzeaman
1
@bzeaman In der Fußnote heißt es, dass Sie ein Zeichenfolgenliteral erstellen können, das nicht der Definition von 7.1.1 für eine Zeichenfolge entspricht. Der darauf bezogene Satz besagt jedoch, dass kompatible Compiler diese NULbeenden, egal was passiert: "In der Übersetzungsphase 7 ein Byte oder Code Der Wert Null wird an jede Multibyte-Zeichenfolge angehängt, die sich aus einem Zeichenfolgenliteral oder Literalen ergibt. " Bibliotheksfunktionen, die die Definition von 7.1.1 verwenden, hören beim ersten NULAuffinden auf und wissen nicht, dass zusätzliche Zeichen darüber hinaus vorhanden sind.
Blrfl
Ich stehe korrigiert. Ich habe nach verschiedenen Begriffen wie 'null' gesucht, aber 6.4.5.5 verpasst und den 'Wert null' erwähnt.
Bzeaman
3

Ich arbeite mit eingebetteten Systemen ... ohne Betriebssystem ... Ich verwende ... die Idee, NULL-terminierte Zeichenzeiger zu haben und sie als Zeichenfolgen zu behandeln, bei denen NULL das Ende bedeutet. Ich weiß, dass dies ziemlich häufig ist, aber können Sie sich immer darauf verlassen, dass dies der Fall ist?

In der Sprache C gibt es keinen Zeichenfolgendatentyp, aber Zeichenfolgenliterale .

Wenn Sie ein Zeichenfolgenliteral in Ihr Programm einfügen, wird es normalerweise mit NUL beendet (siehe jedoch den Sonderfall, der in den Kommentaren unten erläutert wird). Das heißt, wenn Sie "foobar"an einer Stelle const char *einfügen, an der ein Wert erwartet wird, wird der Compiler ausgegeben foobar⊘auf das const / code-Segment / den Abschnitt Ihres Programms, und der Wert des Ausdrucks ist ein Zeiger auf die Adresse, an der das fZeichen gespeichert wurde . (Hinweis: Ich verwende , um das NUL-Byte zu kennzeichnen.)

Der einzige andere Sinn, in dem die C-Sprache Zeichenfolgen enthält, besteht darin, dass sie einige Standardbibliotheksroutinen enthält, die mit NUL-terminierten Zeichenfolgen arbeiten. Diese Bibliotheksroutinen existieren in einer Bare-Metal-Umgebung nur, wenn Sie sie selbst portieren.

Sie sind nur Code - nicht anders als der Code, den Sie selbst schreiben. Wenn Sie sie beim Portieren nicht beschädigen, tun sie das, was sie immer tun (z. B. halten Sie an einem NUL an).

Solomon Slow
quelle
2
Betreff: "Wenn Sie ein Zeichenfolgenliteral in Ihr Programm einfügen, wird es immer mit NUL beendet.": Sind Sie sich da sicher? Ich bin mir ziemlich sicher, dass (z. B.) char foo[4] = "abcd";eine gültige Methode ist, um ein nicht nullterminiertes Array mit vier Zeichen zu erstellen.
Ruakh
2
@ruakh, Ups! Das ist ein Fall, den ich nicht berücksichtigt habe. Ich dachte an ein String-Literal, das an einer Stelle erscheint, an der ein char const * Ausdruck erwartet wird. Ich habe vergessen, dass C- Initialisierer manchmal anderen Regeln folgen können.
Solomon Slow
@ruakh Das String-Literal ist NUL-terminiert. Das Array ist nicht.
Jamesdlin
2
@ruakh du hast eine char[4]. Das ist keine Zeichenfolge, aber sie wurde von einer
Caleth
2
@Caleth, "von eins initialisiert" muss nicht zur Laufzeit passieren. Wenn wir das Stichwort hinzufügen staticzu Ruakh des Beispiel, dann wird der Compiler kann emittieren ein nicht NUL „ABCD“ zu einem initialisierten Datensegment beendet , so dass der Variable vom Programm - Loader initialisiert wird. Ruakh hatte also Recht: Es gibt mindestens einen Fall, in dem das Erscheinen eines String-Literal in einem Programm nicht erfordert, dass der Compiler einen NUL-terminierten String ausgibt. (ps, ich habe das Beispiel tatsächlich mit gcc 5.4.0 kompiliert, und der Compiler hat die NUL nicht ausgegeben.)
Solomon Slow
2

Wie andere bereits erwähnt haben, ist die Nullterminierung von Zeichenfolgen eine Konvention der C-Standardbibliothek. Sie können mit Zeichenfolgen beliebig umgehen, wenn Sie die Standardbibliothek nicht verwenden.

Dies gilt für jedes Betriebssystem mit einem C-Compiler. Sie können auch C-Programme schreiben, die nicht unter einem echten Betriebssystem ausgeführt werden, wie Sie in Ihrer Frage erwähnt haben. Ein Beispiel wäre der Controller für einen Tintenstrahldrucker, den ich einmal entworfen habe. In eingebetteten Systemen ist der Speicheraufwand eines Betriebssystems möglicherweise nicht erforderlich.

In speicherarmen Situationen würde ich zum Beispiel die Eigenschaften meines Compilers gegenüber dem Befehlssatz des Prozessors betrachten. In einer Anwendung, in der Zeichenfolgen häufig verarbeitet werden, kann es wünschenswert sein, Deskriptoren wie die Zeichenfolgenlänge zu verwenden. Ich denke an einen Fall, in dem die CPU besonders effizient mit kurzen Offsets und / oder relativen Offsets mit Adressregistern arbeitet.

Was ist in Ihrer Anwendung wichtiger: Codegröße und -effizienz oder Kompatibilität mit einem Betriebssystem oder einer Bibliothek? Eine weitere Überlegung könnte die Wartbarkeit sein. Je weiter Sie von der Konvention abweichen, desto schwieriger wird es für andere, diese aufrechtzuerhalten.

Hugh Buntu
quelle
1

Andere haben das Problem angesprochen, dass in C Zeichenfolgen größtenteils das sind, was Sie daraus machen. Aber Ihre Frage bezüglich des Terminators selbst scheint etwas verwirrend zu sein, und aus einer Perspektive könnte dies das sein, worüber sich jemand in Ihrer Position Sorgen macht.

C-Strings sind nullterminiert. Das heißt, sie werden durch das Nullzeichen abgeschlossen NUL. Sie werden nicht durch den Nullzeiger abgeschlossen NULL, der eine völlig andere Art von Wert mit einem völlig anderen Zweck darstellt.

NUList garantiert, den ganzzahligen Wert Null zu haben. Innerhalb der Zeichenfolge hat sie auch die Größe des zugrunde liegenden Zeichentyps, die normalerweise 1 beträgt.

NULLEs wird nicht garantiert, dass es einen ganzzahligen Typ gibt. NULList für die Verwendung in einem Zeigerkontext vorgesehen und es wird allgemein erwartet, dass es einen Zeigertyp hat, der nicht in ein Zeichen oder eine Ganzzahl konvertiert werden sollte, wenn Ihr Compiler gut ist. Während die Definition von NULLdas Glyphen beinhaltet 0, ist nicht garantiert, dass es tatsächlich diesen Wert hat [1], und es sei denn, Ihr Compiler implementiert die Konstante als ein Zeichen #define(viele nicht, weil es in einem Nicht -Zeichen NULL wirklich nicht sinnvoll sein sollte Zeigerkontext), es ist daher nicht garantiert, dass der erweiterte Code tatsächlich einen Nullwert enthält (obwohl er verwirrenderweise einen Null-Glyphen enthält).

Wenn NULLes eingegeben wird, ist es auch unwahrscheinlich, dass es eine Größe von 1 (oder eine andere Zeichengröße) hat. Dies kann möglicherweise zusätzliche Probleme verursachen, obwohl die tatsächlichen Zeichenkonstanten zum größten Teil auch keine Zeichengröße haben.

Jetzt werden die meisten Leute dies sehen und denken: "Nullzeiger als etwas anderes als All-Null-Bits? Was für ein Unsinn" - aber solche Annahmen sind nur auf gängigen Plattformen wie x86 sicher. Da Sie ausdrücklich ein Interesse an der Ausrichtung auf andere Plattformen erwähnt haben, müssen Sie dieses Problem berücksichtigen, da Sie Ihren Code explizit von Annahmen über die Art der Beziehung zwischen Zeigern und Ganzzahlen getrennt haben.

Während C-Zeichenfolgen nullterminiert sind, werden sie daher nicht durch NULL, sondern durch NUL(normalerweise geschrieben '\0') terminiert . Code, der explizit NULLals String-Terminator verwendet wird, funktioniert auf Plattformen mit einer einfachen Adressstruktur und wird sogar mit vielen Compilern kompiliert, ist jedoch absolut nicht korrekt. C.


[1] Der tatsächliche Nullzeigerwert wird vom Compiler eingefügt, wenn er ein 0 Token in einem Kontext liest, in dem es in einen Zeigertyp konvertiert wird. Dies ist keine Konvertierung vom ganzzahligen Wert 0 und kann nicht garantiert werden, wenn etwas anderes als das Token 0selbst verwendet wird, z. B. ein dynamischer Wert aus einer Variablen. Die Konvertierung ist auch nicht umkehrbar, und ein Nullzeiger muss bei der Konvertierung in eine Ganzzahl nicht den Wert 0 ergeben.

Leushenko
quelle
Toller Punkt. Ich habe eine Bearbeitung eingereicht, um dies zu klären.
Monty Harder
"Es NUList garantiert, dass der ganzzahlige Wert Null ist." -> C definiert nicht NUL. Stattdessen definiert C, dass Strings einen endgültigen Null-Chracter haben , ein Byte, bei dem alle Bits auf 0 gesetzt sind.
chux - Monica
1

Ich habe eine Zeichenfolge in C verwendet. Dies bedeutet, dass Zeichen mit Nullterminierung als Zeichenfolgen bezeichnet werden.

Es gibt keine Probleme, wenn Sie in Baremetal oder in Betriebssystemen wie Windows, Linux, RTOS: (FreeRTO, OSE) verwenden.

In der eingebetteten Welt hilft die Nullterminierung tatsächlich mehr, das Zeichen als Zeichenfolge zu kennzeichnen.

Ich habe in vielen sicherheitskritischen Systemen solche Zeichenfolgen in C verwendet.

Sie fragen sich vielleicht, was ist eigentlich ein String in C?

C-artige Strings, die Arrays sind, gibt es auch String-Literale wie "this". In Wirklichkeit sind diese beiden Zeichenfolgentypen lediglich Sammlungen von Zeichen, die im Speicher nebeneinander sitzen.

Wenn Sie eine Zeichenfolge in doppelten Anführungszeichen schreiben, erstellt C automatisch ein Array von Zeichen für uns, das diese Zeichenfolge enthält und mit dem Zeichen \ 0 abgeschlossen ist.

Sie können beispielsweise ein Array von Zeichen deklarieren, definieren und mit einer Zeichenfolgenkonstante initialisieren:

char string[] = "Hello cruel world!";

Unkomplizierte Antwort: Sie müssen sich nicht wirklich um die Verwendung von Zeichen mit Nullterminierung kümmern, dies funktioniert unabhängig von einer Plattform.

baumelnder Zeiger
quelle
Danke, wusste nicht, dass bei der Deklaration mit doppelten Anführungszeichen a NULautomatisch angehängt wird.
Snoop
1

Wie andere gesagt haben, ist die Nullterminierung für Standard C ziemlich universell. Aber (wie andere auch betont haben) nicht 100%. Für (ein anderes) Beispiel verwendete das VMS-Betriebssystem normalerweise sogenannte "String-Deskriptoren" http://h41379.www4.hpe.com/commercial/c/docs/5492p012.html, auf die in C über #include <descip.h zugegriffen wurde >

Inhalte auf Anwendungsebene können eine Nullterminierung verwenden oder nicht, der Entwickler hält dies jedoch für richtig. Für VMS-Inhalte auf niedriger Ebene sind jedoch unbedingt Deskriptoren erforderlich, die überhaupt keine Nullterminierung verwenden (Einzelheiten siehe Link oben). Dies ist weitgehend so, dass alle Sprachen (C, Assembly usw.), die direkt VMS-Interna verwenden, eine gemeinsame Schnittstelle mit ihnen haben können.

Wenn Sie also eine ähnliche Situation erwarten, sollten Sie etwas vorsichtiger sein, als es eine "universelle Nullterminierung" vermuten lässt. Ich wäre vorsichtiger, wenn ich das tun würde, was Sie tun, aber für meine Sachen auf Anwendungsebene ist es sicher, eine Nullbeendigung anzunehmen. Ich würde Ihnen einfach nicht das gleiche Maß an Sicherheit vorschlagen. Ihr Code muss möglicherweise zu einem späteren Zeitpunkt mit Assembly- und / oder anderem Sprachcode verbunden werden, was möglicherweise nicht immer dem C-Standard für nullterminierte Zeichenfolgen entspricht.

John Forkosh
quelle
Heute ist eine 0-Kündigung eigentlich ziemlich ungewöhnlich. C ++ std :: string nicht, Java String nicht, Objective-C NSString nicht, Swift String nicht - daher unterstützt jede Sprachbibliothek Zeichenfolgen mit NUL-Codes innerhalb der Zeichenfolge (was mit C unmöglich ist Saiten aus offensichtlichen Gründen).
Gnasher729
@ gnasher729 Ich habe "... ziemlich universell" in "ziemlich universell für Standard C" geändert, was hoffentlich alle Unklarheiten beseitigt und bis heute korrekt bleibt (und das habe ich gemäß dem Thema und der Frage des OP gemeint).
John Forkosh
0

Nach meiner Erfahrung mit eingebetteten, sicherheitskritischen und Echtzeitsystemen ist es nicht ungewöhnlich, sowohl die C- als auch die PASCAL-Zeichenfolgenkonvention zu verwenden, dh die Zeichenfolgenlänge als erstes Zeichen anzugeben (was die Länge auf 255 begrenzt) und das zu beenden Zeichenfolge mit mindestens einem 0x00 ( NUL), wodurch die verwendbare Größe auf 254 reduziert wird.

Ein Grund dafür ist zu wissen, wie viele Daten Sie nach dem Empfang des ersten Bytes erwarten, und ein anderer Grund ist, dass in solchen Systemen dynamische Puffergrößen nach Möglichkeit vermieden werden - die Zuweisung einer festen Puffergröße von 256 ist schneller und sicherer (Nr müssen überprüfen, ob mallocfehlgeschlagen). Ein weiterer Grund ist, dass die anderen Systeme, mit denen Sie kommunizieren, möglicherweise nicht in ANSI-C geschrieben sind.

Bei jeder eingebetteten Arbeit ist es wichtig, so schnell wie möglich ( idealerweise vor dem Start ) ein Interface Control Document (IDC) einzurichten und zu verwalten, das alle Ihre Kommunikationsstrukturen einschließlich Zeichenfolgenformaten, Endianness, Ganzzahlgrößen usw. definiert. und es sollte Ihr und alle Teams heiliges Buch sein, wenn Sie das System schreiben - wenn jemand eine neue Struktur oder ein neues Format einführen möchte, muss es zuerst dort dokumentiert und jeder, der betroffen sein könnte, informiert werden, möglicherweise mit der Option, gegen die Änderung ein Veto einzulegen .

Steve Barnes
quelle