ubuntuusers.de

[C] Alternative zu atoi() gesucht

Status: Gelöst | Ubuntu-Version: Nicht spezifiziert
Antworten |

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Hi,

ich möchte Eingaben, die der Benutzer von der Kommandozeile übergibt, in Integer umwandeln. Hier würde sich ja erstmal atoi() anbieten. Leider verhält sich die Funktion etwas komisch, wenn sie einen nicht-nummerischen Wert als String erhält: Statt einem NULL-Zeiger (der mir wesentlich lieber wäre), gibt sie einen Nullwert (also die Zahl 0) zurück. Da in meinem Fall Nutzereingaben mit 0 (es geht um das Vergleichen von Programmversionen) keine Seltenheit sind, ist das natürlich etwas ungünstig für mich. Ich möchte nämlich gerne eine Fehlermeldung werfen, wenn an der Stelle etwas Falsches ankommt (und nicht 0 eintragen, wenn vielleicht gar nicht 0 gemeint war → Tippfehler). Welche Möglichkeiten habe ich da?

epyx

Avatar von epyx

Anmeldungsdatum:
22. März 2007

Beiträge: 48

Könntest natürlich jedes Zeichen überprüfen ob es ein numerisches Zeichen ist. Also mit isdigit

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
bool isNumber(char* zahl){
    char flag=0,*p=zahl;
    if(!*p) return false;
    if(*p=='-'||*p=='+')++p;
    for(;*p;++p)
        if(!isdigit(*p))
            if(flag || *p!='.' )return false;
            else ++flag;
    return true;
}

Lunar

Anmeldungsdatum:
17. März 2006

Beiträge: 5792

strtol() ist wohl das, was du suchst:

1
2
3
4
5
6
7
8
const char * input =  // Eingabe einlesen
char *tailptr = 0;
long int number = strtol(input, &tailptr ,10);
if ((*tailptr) == '\0') {
    // die Eingabe enthielt nur Zahlen
} else {
    // die Eingabe enthielt ungültige Zeichen
}

Korrekterweise muss man in jedem Fall noch errno auf ERANGE prüfen, und die Rückgabe mit INT_MAX und INT_MIN vergleichen, wenn man anschließend in einen int casten möchte.

Hello_World

Anmeldungsdatum:
13. Juni 2006

Beiträge: 3620

boost::lexical_cast<int>("42");
*duck und weg*

snafu1

(Themenstarter)
Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

@Lunar: Danke, ich denke das funktioniert. Ich prüfe jetzt vorher, ob es ein "reiner" Nummernstring ist, indem ich strtol() vergewaltige und wende bei positivem Ergebnis atoi() an:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

/* Workaround to detect non-numerical chars, since the string-cast functions
   don't distinguish between errors and "real" zeros when returning 0 */

int
is_numstring (const char *string)
{
    char *end;

    end = NULL;
    strtol (string, &end, 10);

    return ((*end) == '\0');
}

int
main (int argc, char *argv[])
{
    char *string;

    string = argv[1];
    if (is_numstring (string)) {
        printf ("%i\n", atoi (string));
        return 0;
    } else {
        fprintf (stderr, "%s: found non-numerical chars\n", argv[0]);
        return 1;
    }
}

snafu1

(Themenstarter)
Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Ich bräuchte hier doch nochmal Hilfe. Ich dachte, dass ich mir für die Version einen struct erzeuge, der die Informationen für Major, Minor und Micro enthält. Ich nehme hier Strings, um NULL-Zeiger einsetzen zu können. Aber soweit komme ich nicht mal, da ich schon vorher reichlich Fehlermeldungen erhalte, wenn ich versuche, die Eingabe zu parsen:

1
2
3
4
5
6
7
8
$ gcc test2.c -o test2
test2.c: In Funktion »string_to_version«:
test2.c:28: Warnung: Initialisierung erzeugt Zeiger von Ganzzahl ohne Typkonvertierung
test2.c:35: Warnung: Initialisierung erzeugt Zeiger von Ganzzahl ohne Typkonvertierung
test2.c:42: Warnung: Initialisierung erzeugt Zeiger von Ganzzahl ohne Typkonvertierung
test2.c:60: Fehler: expected declaration or statement at end of input
test2.c:60: Fehler: expected declaration or statement at end of input
test2.c:60: Fehler: expected declaration or statement at end of input

Ich kann mir das nicht erklären, da laut diesen Infos doch eigentlich ein char-Array zurückgeliefert werden soll. Seht mir bitte nach, dass ich Anfänger bin... ☹

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>

/* This holds the version information as strings
   NOTE: minor and micro may be NULL pointers */
typedef struct {
    char *major;
    char *minor;
    char *micro;
} Version;

/* Workaround to detect non-numerical chars, since the string-cast functions
   don't distinguish between errors and "real" zeros when returning 0 */
int
is_numstring (const char *string)
{
    char *end = NULL;
    strtol (string, &end, 10);
    return ((*end) == '\0');
}

/* Parse string like "0.2.3" to Version struct */
Version *
string_to_version (char *version_string)
{
    Version *version;

    /* Major */
    char *major = strtok (version_string, ".");
    if (is_numstring (major)) {
        version->major = major;
    } else {
        version->major = NULL;

    /* Minor */
    char *minor = strtok (NULL, ".");
    if (is_numstring (minor)) {
        version->minor = minor;
    } else {
        version->minor = NULL;

    /* Micro */
    char *micro = strtok (NULL, ".");
    if (is_numstring (micro)) {
        version->micro = micro;
    } else {
        version->micro = NULL;

    return version;
}

int
main (int argc, char *argv[])
{
    char *version_string = argv[1];
    Version *version = string_to_version (version_string);

    printf ("major: %s\nminor: %s\nmicro: %s\n",
            version->major, version->minor, version->micro);
    return 0;
}

EDIT: Die Fehlermeldungen für Zeile 60 kamen, weil ich die schließenden Klammern für die else-Anweisungen vergessen hatte.

radoe2

Anmeldungsdatum:
30. November 2006

Beiträge: 243

snafu1 schrieb:

Ich bräuchte hier doch nochmal Hilfe. Ich dachte, dass ich mir für die Version einen struct erzeuge, der die Informationen für Major, Minor und Micro enthält. Ich nehme hier Strings, um NULL-Zeiger einsetzen zu können. Aber soweit komme ich nicht mal, da ich schon vorher reichlich Fehlermeldungen erhalte, wenn ich versuche, die Eingabe zu parsen:

1
2
3
4
5
6
7
8
$ gcc test2.c -o test2
test2.c: In Funktion »string_to_version«:
test2.c:28: Warnung: Initialisierung erzeugt Zeiger von Ganzzahl ohne Typkonvertierung
test2.c:35: Warnung: Initialisierung erzeugt Zeiger von Ganzzahl ohne Typkonvertierung
test2.c:42: Warnung: Initialisierung erzeugt Zeiger von Ganzzahl ohne Typkonvertierung
test2.c:60: Fehler: expected declaration or statement at end of input
test2.c:60: Fehler: expected declaration or statement at end of input
test2.c:60: Fehler: expected declaration or statement at end of input

EDIT: Die Fehlermeldungen für Zeile 60 kamen, weil ich die schließenden Klammern für die else-Anweisungen vergessen hatte.

Dir fehlt noch ein

#include <string.h>

in deinem Code. Ohne das kennt der Compiler den Prototypen für strtok nicht und nimmt einen Rückgabetyp int an.

snafu1

(Themenstarter)
Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Gut, jetzt kommen keine Fehlermeldungen, aber er segfaulted. ☹

Code hat sich nicht wesentlich verändert:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <string.h>

/* This holds the version information as strings
   NOTE: minor and micro may be NULL pointers */
typedef struct {
    char *major;
    char *minor;
    char *micro;
} Version;

/* Workaround to detect non-numerical chars, since the string-cast functions
   don't distinguish between errors and "real" zeros when returning 0 */
int
is_numstring (const char *string)
{
    char *end = NULL;
    strtol (string, &end, 10);
    return ((*end) == '\0');
}

/* Parse string like "0.2.3" to Version struct */
Version *
string_to_version (char *version_string)
{
    Version *version;

    /* Major */
    char *major = strtok (version_string, ".");
    if (is_numstring (major)) {
        version->major = major;
    } else {
        return NULL; // return NULL, since there's not even a major number
    }

    /* Minor */
    char *minor = strtok (NULL, ".");
    if (is_numstring (minor)) {
        version->minor = minor;
    } else {
        version->minor = NULL;
    }

    /* Micro */
    char *micro = strtok (NULL, ".");
    if (is_numstring (micro)) {
        version->micro = micro;
    } else {
        version->micro = NULL;
    }

    return version;
}

int
main (int argc, char *argv[])
{
    char *version_string = argv[1];
    Version *version = string_to_version (version_string);

    printf ("major: %s\nminor: %s\nmicro: %s\n",
            version->major, version->minor, version->micro);
    return 0;
}
1
2
$ gcc test2.c -o test2 && ./test2 "2.5.4"
Segmentation fault

epyx

Avatar von epyx

Anmeldungsdatum:
22. März 2007

Beiträge: 48

Wie gibt keine Fehler ? Kompilier doch mall mit -Wall

1
2
3
4
test.c: In Funktion »is_numstring«:
test.c:18: Warnung: Implizite Deklaration der Funktion »strtol«
test.c: In Funktion »string_to_version«:
test.c:52: Warnung: »version« may be used uninitialized in this function

Der erste fehler ist banal : #include <stdlib.h>

der zweite, naja du hast zwar eine struct, die auf was zeigt, aber der Speicherbereich wird nicht reserviert. Daher der seg fault.

radoe2

Anmeldungsdatum:
30. November 2006

Beiträge: 243

snafu1 schrieb:

Gut, jetzt kommen keine Fehlermeldungen, aber er segfaulted. ☹

Code hat sich nicht wesentlich verändert:

1
2
#include <stdio.h>
#include <string.h>

Da fehlt auch noch ein

1
#include <stdlib.h>

Damit gibt es auch mit -Wall keine Warnungen mehr.

1
2
$ gcc test2.c -o test2 && ./test2 "2.5.4"
Segmentation fault

Dein Pointer "version" ist uninitialisiert und zeigt ins Nirvana. Lass den auf gültigen Speicher zeigen und schon gehts...

snafu1

(Themenstarter)
Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Stimmt, es muss Version *version = malloc (sizeof (Version)); heißen. ☺

Lunar

Anmeldungsdatum:
17. März 2006

Beiträge: 5792

snafu1 schrieb:

Ich prüfe jetzt vorher, ob es ein "reiner" Nummernstring ist, indem ich strtol() vergewaltige und wende bei positivem Ergebnis atoi() an.

Das muss ich jetzt aber nicht verstehen, oder?

Hello_World

Anmeldungsdatum:
13. Juni 2006

Beiträge: 3620

Meiner Meinung nach ist es sauberer, Version *version = malloc(sizeof *version) zu schreiben. Auf diese Weise muss das Statement nicht geändert werden, wenn der Typ von *version sich ändert.

Ich finde das gesamte Verfahren übrigens etwas merkwürdig. Offenbar verwendest Du in der Version-Struktur nur Zeiger, um einen NULL-Wert angeben zu können. Wieso verwendest Du nicht einfach drei int und füllst Minor und Micro z. B. mit -1, wenn sie nicht vorhanden sind? Ferner bietet es sich an, im vorliegenden Fall die Funktion sscanf zu verwenden. Hier ein ungetestetes Beispiel:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
typedef struct {
    int major;
    int minor;
    int micro;
} Version;

Version *string_to_version(const char *str) {
    Version *ret = malloc(sizeof *ret);
    if (!ret) 
        goto fail;
    int len;
    int n = sscanf("%d%n.%d%n.%d%n", &ret->major, &len, &ret->minor, &len, &ret->micro, &len);
    switch (n) {
    case 0:
        goto fail;
    case 2:
        ret->micro = -1;
    case 1:
        ret->minor = -1;
    }
    if (str[len])
        goto fail;

    return ret;

    fail: 
    free(ret);
    return 0; 
}

snafu1

(Themenstarter)
Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Danke, sscanf() ist genau, was ich brauche. ☺

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <stdlib.h>

/* This holds the version information */
typedef struct {
    int major;
    int minor;
    int micro;
} Version;

/* Parse a string like "0.2.3" and return it as a version struct. The fields for
   minor and/or micro are -1, if that information can not be detected. Strings
   like "0.2a" will be interpreted as "0.2". Everything after an non-numeric
   character will be ignored. The function will return at least the major
   version, or NULL if that fails. */ 
Version *
string_to_version (const char *str)
{
    Version *version = malloc (sizeof *version);
    if (!version) {
        goto error;
    }
    int n = sscanf (str, "%d.%d.%d",
                    &version->major, &version->minor, &version->micro);
    switch (n) {
        case 0:
            goto error;
        case 1:
            version->minor = -1;
            version->micro = -1;
        case 2:
            version->micro = -1;
    }
    return version;
    
    error:
        free (version);
        return NULL;
}

int
main (int argc, char *argv[])
{
    char *vstring = argv[1];
    if (!vstring) {
        fprintf (stderr, "An argument is required\n");
        return 1;
    }
    Version *version = string_to_version (vstring);
    if (!version) {
        fprintf (stderr, "Unable to get version information from '%s'\n", vstring);
        return 1;
    }
    printf ("major: %d\nminor: %d\nmicro: %d\n",
            version->major, version->minor, version->micro);
    free (version);

    return 0;
}
Antworten |