Warnung: Der Vergleich mit Zeichenfolgenliteralen führt zu unspezifischem Verhalten
Lesezeit: 1 Minute
nunos
Ich beginne ein Projekt zum Schreiben einer vereinfachten Shell für Linux in C. Ich kenne mich weder mit C noch mit Linux aus, deshalb habe ich entschieden, dass es eine gute Idee wäre.
Beginnend mit dem Parser bin ich schon auf einige Probleme gestoßen.
Der Code sollte einfach sein, deshalb habe ich keine Kommentare eingefügt.
Ich erhalte eine Warnung mit gcc: “Vergleich mit Zeichenfolgenliteralen führt zu unspezifischem Verhalten” in den Zeilen, die mit “WARNING HERE” kommentiert sind (siehe Code unten).
Ich habe keine Ahnung, warum dies eine Warnung auslöst, aber das eigentliche Problem ist, dass, obwohl ich ein “<" mit einem "<" vergleiche, es nicht in das if ...
Ich suche nach einer Antwort für das erklärte Problem, aber wenn Sie etwas im Code sehen, das verbessert werden sollte, sagen Sie es bitte. Denken Sie nur daran, dass ich nicht so bewandert bin und dass dies noch in Arbeit ist (oder noch besser, in Arbeit ist).
Danke im Voraus.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
typedef enum {false, true} bool;
typedef struct {
char **arg;
char *infile;
char *outfile;
int background;
} Command_Info;
int parse_cmd(char *cmd_line, Command_Info *cmd_info)
{
char *arg;
char *args[100];
int i = 0;
arg = strtok(cmd_line, " \n");
while (arg != NULL) {
args[i] = arg;
arg = strtok(NULL, " \n");
i++;
}
int num_elems = i;
cmd_info->infile = NULL;
cmd_info->outfile = NULL;
cmd_info->background = 0;
int iarg = 0;
for (i = 0; i < num_elems; i++)
{
if (args[i] == "&") //WARNING HERE
return -1;
else if (args[i] == "<") //WARNING HERE
if (args[i+1] != NULL)
cmd_info->infile = args[i+1];
else
return -1;
else if (args[i] == ">") //WARNING HERE
if (args[i+1] != NULL)
cmd_info->outfile = args[i+1];
else
return -1;
else
cmd_info->arg[iarg++] = args[i];
}
cmd_info->arg[iarg] = NULL;
return 0;
}
void print_cmd(Command_Info *cmd_info)
{
int i;
for (i = 0; cmd_info->arg[i] != NULL; i++)
printf("arg[%d]=\"%s\"\n", i, cmd_info->arg[i]);
printf("arg[%d]=\"%s\"\n", i, cmd_info->arg[i]);
printf("infile=\"%s\"\n", cmd_info->infile);
printf("outfile=\"%s\"\n", cmd_info->outfile);
printf("background=\"%d\"\n", cmd_info->background);
}
int main(int argc, char* argv[])
{
char cmd_line[100];
Command_Info cmd_info;
printf(">>> ");
fgets(cmd_line, 100, stdin);
parse_cmd(cmd_line, &cmd_info);
print_cmd(&cmd_info);
return 0;
}
Wenn Sie wirklich nur ein einzelnes Zeichen überprüfen möchten, können Sie dies tun if (args[i] && args[i][0] == '<').
– GManNickG
8. April 2010 um 20:11 Uhr
Sie verwenden möchten strcmp() == 0 um Zeichenfolgen zu vergleichen, anstatt eine einfache ==die nur vergleichen, wenn die Zeiger gleich sind (was in diesem Fall nicht der Fall sein wird).
args[i] ist ein Zeiger auf eine Zeichenfolge (ein Zeiger auf ein Array von Zeichen mit Nullterminierung), so wie es ist "&" oder "<".
Der Ausdruck argc[i] == "&" prüft, ob die beiden Zeiger gleich sind (auf denselben Speicherplatz zeigen).
Der Ausdruck strcmp( argc[i], "&") == 0 prüft, ob der Inhalt der beiden Strings gleich ist.
Ein anständiger Compiler könnte es sagen;) stackoverflow.com/questions/2603039/…
– jfs
8. April 2010 um 20:29 Uhr
@JF Sebastian: Während diese zusätzlichen Hinweise von Clang gut aussehen, hat gcc hier perfekt funktioniert – die Zeilennummern sind angegeben, und es gibt nur einen Vergleich für diese Zeilen.
– Kaskabel
8. April 2010 um 22:32 Uhr
Würde es funktionieren, die beiden Zeiger vor dem Vergleich zu dereferenzieren?
– JobHunter69
8. Januar 2018 um 17:01 Uhr
In Ihren Beispielen meinen Sie wahrscheinlich argvnicht argc.
– Vstrom-Codierer
9. August um 20:12 Uhr
RarrRarrRarr
Es wird unterschieden zwischen 'a' und "a":
'a' bedeutet den Wert des Charakters a.
"a" bedeutet die Adresse des Speicherplatzes, an dem sich die Zeichenfolge befindet "a" gespeichert wird (was sich im Allgemeinen im Datenbereich des Speicherplatzes Ihres Programms befindet). An dieser Speicherstelle haben Sie zwei Bytes – das Zeichen 'a' und das Null-Terminator für die Zeichenfolge.
Argumente[i] ist ein char*kein char.
– CB Bailey
8. April 2010 um 20:07 Uhr
if (args[i] == "&")
Ok, lassen Sie uns sezieren, was das tut.
args ist ein Array von Zeigern. Hier vergleichen Sie also args[i] (ein Zeiger) auf "&" (auch ein Zeiger). Nun, das wird nur wahr, wenn Sie es irgendwo haben args[i]="&" und selbst dann, "&" nicht garantiert überall auf die gleiche Stelle zeigt.
Ich glaube, was Sie eigentlich suchen, ist beides strcmp um die gesamte Zeichenfolge zu vergleichen oder was Sie tun möchten if (*args[i] == '&') um das erste Zeichen der zu vergleichen args[i] Schnur an die & Charakter
Sie können Zeichenfolgen nicht mit vergleichen == in C. Für C sind Zeichenfolgen nur (nullterminierte) Arrays, daher müssen Sie Zeichenfolgenfunktionen verwenden, um sie zu vergleichen. Siehe die Manpage für strcmp() und strncmp().
Wenn Sie ein Zeichen vergleichen möchten, müssen Sie mit einem Zeichen vergleichen, nicht mit einer Zeichenfolge. "a" ist die Saite adie zwei Bytes belegt (die a und das abschließende Nullbyte), während das Zeichen a wird vertreten durch 'a' in C.
klirren hat Vorteile bei der Fehlerberichterstattung und -wiederherstellung.
$ clang errors.c
errors.c:36:21: warning: result of comparison against a string literal is unspecified (use strcmp instead)
if (args[i] == "&") //WARNING HERE
^~ ~~~
strcmp( , ) == 0
errors.c:38:26: warning: result of comparison against a string literal is unspecified (use strcmp instead)
else if (args[i] == "<") //WARNING HERE
^~ ~~~
strcmp( , ) == 0
errors.c:44:26: warning: result of comparison against a string literal is unspecified (use strcmp instead)
else if (args[i] == ">") //WARNING HERE
^~ ~~~
strcmp( , ) == 0
Es schlägt vor zu ersetzen x == y durch strcmp(x,y) == 0.
gengetopt schreibt Befehlszeilenoptionsparser für Sie.
Dies ist eine alte Frage, aber ich musste sie kürzlich jemandem erklären, und ich dachte, die Antwort hier aufzuzeichnen, wäre zumindest hilfreich, um zu verstehen, wie C funktioniert.
Zeichenfolgenliterale wie
"a"
oder
"This is a string"
werden in die Text- oder Datensegmente Ihres Programms eingefügt.
Eine Zeichenfolge in C ist eigentlich ein Zeiger auf ein Zeichen, und die Zeichenfolge wird als die nachfolgenden Zeichen im Speicher verstanden, bis ein NUL-Zeichen gefunden wird. Das heißt, C weiß nicht wirklich etwas über Strings.
Also wenn ich habe
char *s1 = "This is a string";
dann ist s1 ein Zeiger auf das erste Byte der Zeichenfolge.
Nun, wenn ich habe
char *s2 = "This is a string";
Dies ist auch ein Zeiger auf dasselbe erste Byte dieser Zeichenfolge im Text- oder Datensegment des Programms.
Aber wenn ich habe
char *s3 = malloc( 17 );
strcpy(s3, "This is a string");
dann ist s3 ein Zeiger auf eine andere Stelle im Speicher, in die ich alle Bytes der anderen Strings kopiere.
Anschauliche Beispiele:
Obwohl Sie dies, wie Ihr Compiler zu Recht betont, nicht tun sollten, wird Folgendes als wahr ausgewertet:
s1 == s2 // True: we are comparing two pointers that contain the same address
aber das Folgende wird als falsch ausgewertet
s1 == s3 // False: Comparing two pointers that don't hold the same address.
Und obwohl es verlockend sein könnte, so etwas zu haben:
struct Vehicle{
char *type;
// other stuff
}
if( type == "Car" )
//blah1
else if( type == "Motorcycle )
//blah2
Sie sollten es nicht tun, weil es nicht garantiert funktioniert. Auch wenn Sie wissen, dass dieser Typ immer mit einem Zeichenfolgenliteral festgelegt wird.
Ich habe es getestet und es funktioniert. Wenn ich mache
A.type = "Car";
dann wird blah1 ausgeführt und ähnlich für “Motorrad”. Und du könntest Dinge tun wie
if( A.type == B.type )
aber das ist einfach schrecklich. Ich schreibe darüber, weil ich es interessant finde zu wissen, warum es funktioniert, und es hilft zu verstehen, warum man es nicht tun sollte.
Lösungen:
In Ihrem Fall möchten Sie verwenden strcmp(a,b) == 0 ersetzen a == b
In meinem Beispiel sollten Sie eine Aufzählung verwenden.
enum type {CAR = 0, MOTORCYCLE = 1}
Die vorherige Sache mit String war nützlich, weil Sie den Typ drucken konnten, also könnten Sie ein Array wie dieses haben
char *types[] = {"Car", "Motorcycle"};
Und jetzt, wo ich darüber nachdenke, ist dies fehleranfällig, da man darauf achten muss, die gleiche Reihenfolge im Typen-Array beizubehalten.
Daher könnte es besser sein, dies zu tun
char *getTypeString(int type)
{
switch(type)
case CAR: return "Car";
case MOTORCYCLE: return "Motorcycle"
default: return NULL;
}
Ross Youngblood
Ich bin heute auf dieses Problem gestoßen, als ich mit einem Kundenprogramm gearbeitet habe. Das Programm funktioniert FINE in VS6.0 mit dem folgenden: (Ich habe es leicht geändert)
//
// This is the one include file that every user-written Nextest programs needs.
// Patcom-generated files will also look for this file.
//
#include "stdio.h"
#define IS_NONE( a_key ) ( ( a_key == "none" || a_key == "N/A" ) ? TRUE : FALSE )
//
// Note in my environment we have output() which is printf which adds /n at the end
//
main {
char *psNameNone = "none";
char *psNameNA = "N/A";
char *psNameCAT = "CAT";
if (IS_NONE(psNameNone) ) {
output("psNameNone Matches NONE");
output("%s psNameNoneAddr 0x%x \"none\" addr 0x%X",
psNameNone,psNameNone,
"none");
} else {
output("psNameNone Does Not Match None");
output("%s psNameNoneAddr 0x%x \"none\" addr 0x%X",
psNameNone,psNameNone,
"none");
}
if (IS_NONE(psNameNA) ) {
output("psNameNA Matches N/A");
output("%s psNameNA 0x%x \"N/A\" addr 0x%X",
psNameNA,psNameNA,
"N/A");
} else {
output("psNameNone Does Not Match N/A");
output("%s psNameNA 0x%x \"N/A\" addr 0x%X",
psNameNA,psNameNA,
"N/A");
}
if (IS_NONE(psNameCAT)) {
output("psNameNA Matches CAT");
output("%s psNameNA 0x%x \"CAT\" addr 0x%X",
psNameNone,psNameNone,
"CAT");
} else {
output("psNameNA does not match CAT");
output("%s psNameNA 0x%x \"CAT\" addr 0x%X",
psNameNone,psNameNone,
"CAT");
}
}
Wenn in VS6.0 mit Programmdatenbank mit Bearbeiten und Fortfahren integriert. Die Vergleiche scheinen zu funktionieren. Mit dieser Einstellung wird das STRING-Pooling aktiviert, und der Compiler optimiert alle STRING-Zeiger so, dass sie AUF DIESELBE ADRESSE ZEIGEN, sodass dies funktionieren kann. Alle nach der Kompilierzeit erstellten Zeichenfolgen haben UNTERSCHIEDLICHE Adressen, sodass der Vergleich fehlschlägt.
Wenn Sie die Einstellung auf „Nur Programmdatenbank“ ändern, wird das Programm so erstellt, dass es fehlschlägt.
14120700cookie-checkWarnung: Der Vergleich mit Zeichenfolgenliteralen führt zu unspezifischem Verhaltenyes
Wenn Sie wirklich nur ein einzelnes Zeichen überprüfen möchten, können Sie dies tun
if (args[i] && args[i][0] == '<')
.– GManNickG
8. April 2010 um 20:11 Uhr