[Powered by Google Translate] [CS50 bibliotek]
[Nate Hardison] [Harvard University]
[Detta är CS50. CS50.TV]
Den CS50 Biblioteket är ett användbart verktyg som vi har installerat på apparaten
att göra det lättare för dig att skriva program som snabba användare för inmatning.
I den här videon kommer vi dra tillbaka gardinen och titta på vad exakt är i CS50 biblioteket.
>> I videon på C-bibliotek, talar vi om hur du gör # include rubriker filer
av biblioteket i din källkod,
och då du länkar med en binär biblioteksfil under länka fasen
av beräkningen processen.
De header-filer anger gränssnittet biblioteket.
Det vill säga de ingående alla de resurser som biblioteket har tillgängliga för dig att använda,
Liksom funktionsdeklarationer, konstanter och datatyper.
Den binära biblioteksfil innehåller genomförandet av biblioteket,
som sammanställs av bibliotekets header-filer och Bibliotekets. c källkodsfiler.
>> Den binära biblioteksfil är inte särskilt intressant att titta på eftersom det är, ja, i binär.
Så, låt oss ta en titt på header-filer för biblioteket istället.
I det här fallet, det finns bara en header fil som heter cs50.h.
Vi har installerat det i användarens inkluderar katalogen
tillsammans med de andra systemet bibliotekens header-filer.
>> En av de första sakerna du kommer att märka är att cs50.h # omfattar header-filer från andra bibliotek -
float, gränser, standard bool och standard lib.
Återigen, efter principen att inte uppfinna hjulet,
Vi har byggt CS0 biblioteket med hjälp av verktyg som andra för oss.
>> Nästa sak du ser i biblioteket är att vi definierar en ny typ som kallas "sträng".
Denna linje egentligen bara skapar ett alias för char * typ,
så det inte genomsyra magiskt den nya strängen typ med attribut
vanligen förknippas med sträng objekt i andra språk,
såsom längd.
Anledningen till att vi har gjort är att skydda nya programmerare från de blodiga detaljerna
pekare tills de är redo.
>> Nästa del av huvudet filen är deklarationen av de funktioner
att CS50 biblioteket tillhandahåller tillsammans med dokumentation.
Lägg märke till detaljnivå i kommentarerna här.
Detta är super viktigt så att folk vet hur man använder dessa funktioner.
Vi förklarar i sin tur fungerar att uppmana användaren och tecken avkastning, dubbel, flottar, Ints,
lång längtar, och stråkar, med hjälp av vår egen sträng typ.
Enlighet med principen om information hiding,
vi har lagt vår definition i en separat c implementering fil -. cs50.c--
finns i användarens källkatalogen.
Vi har förutsatt att filen så att du kan ta en titt på det,
lära av det, och kompilera den på olika maskiner om du vill,
även om vi tycker att det är bättre att arbeta på apparaten för denna klass.
Hur som helst, låt oss ta en titt på det nu.
>> Funktionerna getchar, GetDouble, getFloat, getInt, och GetLongLong
är alla byggda ovanpå GetString funktionen.
Det visar sig att de alla följer i huvudsak samma mönster.
De använder en while-slinga för att uppmana användaren att en rad av ingång.
De återvänder ett speciellt värde om användaren matar en tom rad.
De försöker att tolka användarens input som lämplig typ,
det är en röding, en dubbel, en flottör etc.
Och då de återvänder antingen resultatet om ingången framgångsrikt tolkas
eller de Fråga igen användaren.
>> På en hög nivå, det finns inget riktigt knepigt här.
Du kanske har skrivit liknande strukturerad kod själv i det förflutna.
Den kanske mest kryptiska utseende del är sscanf samtal som tolkar användarens input.
Sscanf är en del av den ingående formatkonvertering familj.
Den lever i standard io.h och dess uppgift är att tolka en C sträng,
enligt ett visst format, lagrar parse resulterar i variabel
tillhandahålls av uppringaren.
Eftersom de ingående formatkonvertering funktioner är mycket användbara, ofta använda funktioner
som är inte super intuitiv först,
Vi ska gå över hur sscanf fungerar.
>> Det första argumentet till sscanf är en char * - en pekare till ett tecken.
För att funktionen ska fungera korrekt,
det tecknet bör vara det första tecknet i en C sträng,
avslutas med null \ 0 tecken.
Detta är den sträng som ska tolka
Det andra argumentet för sscanf är ett format sträng,
vanligtvis skickas in som en sträng konstant,
och du kanske har sett en sträng så här innan när du använder printf.
En procent tecken i formatsträngen indikerar en formatbeskrivning.
Tecknet omedelbart efter ett procenttecken,
anger C-typ som vi vill sscanf konvertera till.
I getInt ser du att det finns en% d och en% c.
Detta innebär att sscanf försöker en decimal int -% d - och röding - det% c.
För varje formatbeskrivning i formatsträngen,
sscanf förväntar sig en motsvarande resonemang senare i sin argumentation listan.
Detta argument måste peka på en lämpligt skrivit plats
där du vill spara resultatet av konverteringen.
>> Det typiska sättet att göra detta är att skapa en variabel på stacken innan sscanf samtalet
för varje objekt som du vill tolka från strängen
och sedan använda adressen operatören - et-tecknet - att passera pekare
dessa variabler till sscanf anropet.
Du kan se det i getInt vi göra just detta.
Strax före sscanf samtalet deklarerar vi en int som kallas n och en röding samtal c på stacken,
och vi passerar pekare till dem i sscanf samtalet.
Att sätta dessa variabler på stacken är att föredra framför att använda utrymme allokerat
på högen med malloc, eftersom du undviker overhead av malloc samtalet
och du behöver inte oroa dig för läckande minne.
Tecken som inte föregås av ett procenttecken inte ber inte konvertering.
Snarare de lägger bara till formatet specifikationen.
>> Till exempel, om formatsträngen i getInt var en% d istället,
sscanf skulle leta efter bokstaven A följt av en int,
och även om det skulle försöka omvandla int skulle det inte göra något annat med en.
Det enda undantaget är mellanslag.
Tomrum i formatsträngen matcha alla belopp blanktecken -
inget alls.
Så, det är därför kommentaren nämner möjligen med ledande och / eller avslutande blanksteg.
Så kommer vid denna tidpunkt ser det ut som vår sscanf samtal försöker tolka användarens input strängen
genom att kontrollera eventuell ledande mellanslag,
följt av en int som ska konverteras och lagras i int variabeln n
följt av en viss mängd av blanktecken, och följs av ett tecken
lagras i CHAR-variabel c..
>> Hur är det returnerade värdet?
Sscanf kommer tolka inmatningsraden från början till slut,
stanna när den når slutet eller när ett tecken i indata
matchar inte ett format karaktär eller när det inte kan göra en konvertering.
Det är returvärdet används för att skilja när det slutat.
Om det slutade, eftersom det nått slutet av den ingående strängen
innan du gör några omvandlingar och innan att inte matcha en del av formatsträngen,
då den speciella konstanta EOF returneras.
Annars returneras antalet lyckade omvandlingar,
som kan vara 0, 1 eller 2, eftersom vi har bett om två omvandlingar.
I vårt fall vill vi se till att användaren har skrivit in en int och bara en int.
>> Så vill vi sscanf återvända 1. Se varför?
Om sscanf gav 0, då inga omvandlingar gjordes,
så att användaren skrivit något annat än en int i början av inmatningen.
Om sscanf returnerar 2, då användaren har korrekt skriver du den i början av inmatningen,
men de skrev då i vissa icke-blanktecken karaktär efteråt
eftersom% c omvandlingen lyckades.
Wow, det är ganska lång förklaring till en funktionsanrop.
Hur som helst, om du vill ha mer information om sscanf och dess syskon,
kolla in man-sidor, Google, eller både och.
Det finns massor av alternativ formatsträngen
och dessa kan spara dig mycket manuellt arbete när man försöker tolka strängar i C.
>> Den sista funktionen i biblioteket för att titta på är GetString.
Det visar sig att GetString är en knepig funktion för att skriva korrekt,
även om det verkar vara en sådan enkel, vanlig uppgift.
Varför är det så?
Nåväl, låt oss tänka på hur vi ska lagra den linje som användaren skriver i.
Eftersom en sträng är en sekvens av tecken,
Vi kanske vill lagra den i en array på stacken,
men vi skulle behöva veta hur länge arrayen kommer att bli när vi förklara den.
Likaså om vi vill sätta den på högen,
Vi behöver skicka till malloc antalet byte vill vi reservera,
men detta är omöjligt.
Vi har ingen aning om hur många tecken användaren skriver in
innan användaren faktiskt inte skriva dem.
>> En naiv lösning på detta problem är att bara reservera en stor bit av rymden, säger
ett block med 1000 tecken för användarens inmatning,
förutsatt att användaren aldrig skulle skriva in en sträng så länge.
Detta är en dålig idé av två skäl.
Först, förutsatt att användarna oftast inte skriver i strängar så länge,
kan du slösa bort en massa minne.
På moderna maskiner, kan detta inte vara ett problem om du gör detta
i en eller två enstaka fall,
men om du tar användarens inmatning i en slinga och lagring för senare användning,
kan du snabbt suga upp massor av minne.
Dessutom, om det program du skriver är för en mindre dator -
en enhet som en smartphone eller något annat med begränsat minne -
denna lösning kommer att orsaka problem mycket snabbare.
Den andra, allvarligare anledning att inte göra detta är att den lämnar ditt program sårbar
till vad som kallas en buffer overflow attack.
I programmering är ett buffertminne som används för att tillfälligt lagra in-eller utdata,
som i detta fall är vår 1000-char blocket.
Ett buffertspill inträffar när data skrivs förbi slutet av blocket.
>> Till exempel, om en användare faktiskt gör typ i mer än 1000 tecken.
Du kanske har upplevt detta misstag vid programmering med arrayer.
Om du har en mängd 10 Ints, stoppar inget du från att försöka läsa eller skriva
den 15: e Int.
Det finns inga kompileringsvarningar eller fel.
Programmet misstag bara rakt fram och åtkomst till minnet
där den tror den 15: e int kommer att vara, och detta kan skriva över dina andra variabler.
I värsta fall kan du skriva en del av din programmets interna
kontrollmekanismer, för att orsaka ditt program faktiskt exekvera olika instruktioner
än du tänkt.
>> Nu är det inte vanligt att göra detta misstag,
men detta är en ganska vanlig teknik att skurkarna använder för att bryta program
och sätta skadlig kod på andras datorer.
Därför kan vi inte bara använda vår naiva lösning.
Vi behöver ett sätt att förhindra våra program från att sårbara
för ett buffertspill attack.
För att göra detta måste vi se till att våra buffert kan växa när vi läser
mer input från användaren.
Lösningen? Vi använder en buffert hög allokerad.
Eftersom vi kan skala med Ändra storlek på realloc funktionen
och vi håller koll på två nummer - index för nästa tomma plats i bufferten
och längden eller buffertens.
Vi läser i tecken från användaren en i taget med hjälp av fgetc funktionen.
Argumentet att fgetc funktionen tar - stdin - är en referens till den standard input strängen,
vilket är en preconnected inkanal som används för att överföra användarens inmatning
från terminalen till programmet.
>> När användaren skriver i en ny karaktär, kontrollerar vi att se om indexet
av nästa fria plats plus 1 är större än kapaciteten hos bufferten.
Den 1 kommer in för om nästa lediga indexet 5,
då vår buffertens längd måste vara 6 tack vare 0 indexering.
Om vi ​​har slut på utrymme i bufferten, då vi försöker ändra storlek på den,
fördubbling det så att vi skär ner på antalet gånger som vi ändra storlek
om användaren skriver i en riktigt lång sträng.
Om strängen har blivit för lång eller om vi får slut på hög minne,
Vi frigör vår buffert och returnera null.
>> Slutligen, lägga vi kolet till bufferten.
När användaren träffar in eller tillbaka, signalerar en ny rad,
eller den speciella tecken - kontroll d - som signalerar ett slut på ingång,
Vi gör en kontroll för att se om användaren faktiskt skrivit i någonting alls.
Om inte, återvänder vi noll.
Annars eftersom vår buffert är förmodligen större än vi behöver,
i värsta fall är det nästan dubbelt så stor som vi behöver
eftersom vi fördubbla varje gång vi ändrar storlek,
Vi gör en ny kopia av strängen med bara mängden utrymme som vi behöver.
Vi lägger till en extra 1 till malloc samtalet
så att det finns utrymme för den speciella null terminator karaktär - \ 0,
som vi lägga till strängen när vi kopierar i resten av karaktärerna,
använda strncpy istället för strcpy
så att vi kan ange exakt hur många tecken som vi vill kopiera.
Strcpy kopior tills den träffar en \ 0.
Då vi frigöra vår buffert och återsända exemplaret till den som ringer.
>> Vem visste sådan enkel till synes funktion så kan vara komplicerat?
Nu vet du vad som händer i CS50 biblioteket.
>> Mitt namn är Nate Hardison, och detta är CS50.
[CS50.TV]