16 svar
585 visningar
Filipjohanssonn 75 – Fd. Medlem
Postad: 9 jan 2021 13:35 Redigerad: 9 jan 2021 14:13

Säker inmatning. Användaren måste skriva in något

Hej!

Har lite problem med säker inmatning. Har en uppgift där jag ska skapa ett fordonsregister med plats för 10 fordon. Allt annat i mitt program fungerar, men jag har en liten säkerhetsbugg. Det är ett krav på säker inmatning och då vill jag inte att användaren bara ska kunna trycka enter när man ska skriva in informationen om fordonet. 
Med andra ord, jag vill inte att mitt register ska ha fordon med tom information. Så här får det inte se ut. 

Fordonstyp: 
Märke:
Registreringsnummer:
Förnamn:
Efternamn:
Ålder:

Har en inmatningsfunktion som kollar om användare skriver in konstiga tecken. Men vill ha ett till krav så att användaren måste skriva in något. Har testat med villkor som if(strlen(arr[0]) == 0), if(strlen(arr[0]) == '\0'), if(strlen(arr[0]) < NULL) utan reslutat. 
Såhär ser funktionen ut nu:

void getInput(char arr[], int size)
{
    int k = 1;
    do
    {
        fgets(arr, size, stdin);
         k = 1;

        for(int i=0; i < strlen(arr)-1; i++)
        {
            if(isalnum(arr[i]) == 0 || arr[0] != '\0')
            {
                k = 0;
                printf("Felaktig inmatning!\n");
                printf("Du kan enbart använda dig av bokstäver och sifffor.\n");
            }
        }
    } while(k == 0);
}
Laguna Online 30476
Postad: 9 jan 2021 14:47

== 0 och == '\0' är ekvivalenta, men den andra antyder att det man jämför med är en char.

En sträng i C avslutas med Nul-tecknet, alltså '\0'. arr[0] är första tecknet, men om du vill ha längden måste du skicka hela strängen, alltså arr, till strlen. Så du kan göra antingen så här:

if (strlen(arr) == 0)

eller

if (arr[0] == '\0')

Vad du får om du gör strlen(arr[0]) är i stort sett slumpmässigt. Förmodligen får du en krasch när du kör. Om du ber kompilatorn att varna för tveksamma saker (vilket du borde göra) så kommer den att påpeka att argumentet till strlen har fel typ (men det är inte ett fel som stoppar kompileringen).

Peter 1023
Postad: 9 jan 2021 15:16

På mitt system hamnar '\n' i den inlästa strängen när jag använder fgets(). D.v.s. strlen ger 1 och inte 0 om man bara trycker enter. Det är kanske det som ställer till det för dig.

Filipjohanssonn 75 – Fd. Medlem
Postad: 9 jan 2021 15:33

Laguna: Testade med denna kod, men kan fortfarande strunta i att skriva något. 

if(strlen(arr) == 0)
         {
            k = 0;  
            printf("Felaktig inmatning!\n");
            printf("Du skrev inte in något\n");
        }


Peter: Hur kan jag lösa det? Jag använder mig av fgets överallt i mitt  program eftersom det är säkrare en scanf.

Laguna Online 30476
Postad: 9 jan 2021 16:04
Peter skrev:

På mitt system hamnar '\n' i den inlästa strängen när jag använder fgets(). D.v.s. strlen ger 1 och inte 0 om man bara trycker enter. Det är kanske det som ställer till det för dig.

Ja, det tänkte jag inte på. (Jag skulle kunna påstå att jag svarade på det som det frågades efter, men det duger inte att säga så.)

Det där '\n' ställer förmodligen till det även om inmatningen är korrekt, så man kan börja med att göra så här:

char *cp = strchr(arr, '\n');
if (cp != NULL)
    *cp = '\0';

Dvs. avsluta strängen där om det finns ett '\n' i den. Den gamla 'gets', som man inte ska använda, och som inte längre finns i den senaste standarden, gjorde så åt en.

Filipjohanssonn 75 – Fd. Medlem
Postad: 9 jan 2021 16:13
Laguna skrev:
Peter skrev:

På mitt system hamnar '\n' i den inlästa strängen när jag använder fgets(). D.v.s. strlen ger 1 och inte 0 om man bara trycker enter. Det är kanske det som ställer till det för dig.

Ja, det tänkte jag inte på. (Jag skulle kunna påstå att jag svarade på det som det frågades efter, men det duger inte att säga så.)

Det där '\n' ställer förmodligen till det även om inmatningen är korrekt, så man kan börja med att göra så här:

char *cp = strchr(arr, '\n');
if (cp != NULL)
    *cp = '\0';

Dvs. avsluta strängen där om det finns ett '\n' i den. Den gamla 'gets', som man inte ska använda, och som inte längre finns i den senaste standarden, gjorde så åt en.

Okej, men hur ska jag få in den i min funktion. Testade och det blev Segmentation fault: 11 :(

Laguna Online 30476
Postad: 9 jan 2021 16:47

Kan du visa hela funktionen som den ser ut nu?

Du kan också köra programmet i en debugger så ser du var det kraschar och kan kolla varför.

Filipjohanssonn 75 – Fd. Medlem
Postad: 9 jan 2021 16:53
Laguna skrev:

Kan du visa hela funktionen som den ser ut nu?

Du kan också köra programmet i en debugger så ser du var det kraschar och kan kolla varför.

void getInput(char arr[], int size)
{
    int k = 1;
    do
    {
        fgets(arr, size, stdin);
        k = 1;
        char *cp = strchr(arr, '\n');
        if (cp != NULL)
        {
            k = 0;
            *cp = '\0';
        }
        for(int i=0; i < strlen(arr)-1; i++)
        {
            if(isalnum(arr[i]) == 0 || strlen(arr) == 0) 
            {
                k = 0;
                printf("Felaktig inmatning!\n");
                printf("Du kan enbart använda dig av bokstäver och sifffor.\n");
            }
        }
    } while(k == 0);
}
Peter 1023
Postad: 9 jan 2021 16:58

Gör denna utskrift på lämpligt ställe:

printf("strlen(arr)-1= %zu \n",strlen(arr)-1);

Laguna Online 30476
Postad: 9 jan 2021 19:10

Om man använder gcc och kompilerar med -Wall -Wextra så får man veta vad problemet är.

Dessutom, att sätta k = 0 när man har hittat en '\n' är nog inte rätt. Raden avslutas i stort sett alltid med '\n'.

Peter 1023
Postad: 9 jan 2021 20:17 Redigerad: 9 jan 2021 20:18

Jag vet inte vad du pluggar (introduktion till programmering eller något annat) men om du tycker att detta är krångligt så kan det kanske vara en tröst att de flesta högnivåspråk har abstraherat bort denna typ av problem. C är inget lämpligt nybörjarspråk (tycker jag). En hel del beror på vilken kompilator man väljer och vilken målarkitektur man bygger för (gissningsvis är detta inget problem här, både jag och Laguna verkar se samma problem trots att vi använder g++ resp gcc, och du verkar sitta på en mac) och man behöver veta mycket om hur en dator arbetar. Hur den representerar tal och hanterar dem, t.ex. här att (uint)0 - 1 = INT_MAX (vilket man kan ha matematiska synpunkter på...). Det gäller troligen högnivåspråk också men i dem behöver man inte programmera på en sådan här detaljerad nivå. Där finns strukturer/klasser som har abstraherat bort detaljerna och ger bättre kompileringsfel/varningar. MEN, om du läser något datorvetenskapligt program så behöver du troligen förstå varför du ser det som du ser.

Lindehaven 820 – Lärare
Postad: 9 jan 2021 20:32

Det finns några olika orsaker till segmentation fault

Gissar att det i detta fall kraschar på denna rad:

*cp = '\0';

Den kod som anropar funktionen behöver allokera minnet för arr med storleken size.

Laguna Online 30476
Postad: 9 jan 2021 20:41 Redigerad: 9 jan 2021 20:42
Lindehaven skrev:

Det finns några olika orsaker till segmentation fault

Gissar att det i detta fall kraschar på denna rad:

*cp = '\0';

Den kod som anropar funktionen behöver allokera minnet för arr med storleken size.

I så fall skulle redan fgets krascha. 

Felet är som Peter konstaterade att strlen(arr)-1 med tom sträng returnerar en (unsigned long) -1, vilket gör att loopen går igenom allt möjligt i minnet tills den träffar på en adress där det inte finns något minne allokerat. 

Lindehaven 820 – Lärare
Postad: 12 jan 2021 13:10
Laguna skrev:
Lindehaven skrev:

Det finns några olika orsaker till segmentation fault

Gissar att det i detta fall kraschar på denna rad:

*cp = '\0';

Den kod som anropar funktionen behöver allokera minnet för arr med storleken size.

I så fall skulle redan fgets krascha. 

Felet är som Peter konstaterade att strlen(arr)-1 med tom sträng returnerar en (unsigned long) -1, vilket gör att loopen går igenom allt möjligt i minnet tills den träffar på en adress där det inte finns något minne allokerat. 

Om arr inte allokerats så kraschar fgets() ändå inte men strchr() gör det. Och så även strlen() om koden kommer så långt vilket den inte gör eftersom den kraschar tidigare på strchr(). Provade med följande kod i Code::Blocks med gcc 

int main()
{
    char *arr; // Inget minne allokerat!
    printf("%x\n", fgets(arr, 5, stdin));
    printf("%x\n", strchr(arr, '\n'));
    printf("%zu\n", strlen(arr));
    return 0;
}
Laguna Online 30476
Postad: 12 jan 2021 15:57

Kul fel. Om fgets inte kraschar skulle man tro att den har lyckats skriva in raden någonstans i minnet som refereras av 'arr', och varför kraschar i så fall strchr?

Eftersom strlen gick bra för frågeställaren så antar jag att 'arr' faktiskt är allokerad.

Lindehaven 820 – Lärare
Postad: 12 jan 2021 16:20
Laguna skrev:

Kul fel. Om fgets inte kraschar skulle man tro att den har lyckats skriva in raden någonstans i minnet som refereras av 'arr', och varför kraschar i så fall strchr?

fgets() returnerar direkt med NULL (utan att man trycker ENTER) så det finns uppenbarligen kod i fgets() som kollar om arr == NULL så att den inte förstör något. Även strchr() och strlen() borde vara defensivt programmerade men är uppenbarligen inte det.

Eftersom strlen gick bra för frågeställaren så antar jag att 'arr' faktiskt är allokerad.

Ja, så är det antagligen.

Laguna Online 30476
Postad: 13 jan 2021 13:09

Råd till trådskaparen: det är bra att alltid kolla returvärdet från funktioner som returnerar något, även om man tror att inget kan gå fel, som fgets i det här fallet.

Svara
Close