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);
}
== 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).
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.
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.
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.
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 :(
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.
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);
}
Gör denna utskrift på lämpligt ställe:
printf("strlen(arr)-1= %zu \n",strlen(arr)-1);
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'.
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.
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
.
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 storlekensize
.
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.
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 storlekensize
.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;
}
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.
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.
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.