© Harry Broeders.
Deze pagina is bestemd voor studenten van de Haagse Hogeschool - Academie voor Technology, Innovation & Society Delft.
In de programmeertaal C kun je 1 karakter opslaan in een variabele van het
type char
. Voor het opslaan van een stukje tekst is in
veel programmeertalen een speciaal type (meestal string genoemd) gedefinieerd.
C heeft echter niet z'n speciaal type. Een stukje tekst kan in C worden
opgeslagen in een char
array. In het onderstaande programma
chararray.c
wordt een character array gevuld
met de letters van een woord dat vervolgens wordt afgedrukt en omgekeerd
wordt afgedrukt.
#include <stdio.h>
int main(void) {
char tekst[5];
int index;
tekst[0] = 'H';
tekst[1] = 'a';
tekst[2] = 'l';
tekst[3] = 'l';
tekst[4] = 'o';
for (index = 0; index < 5; index++) {
printf("%c", tekst[index]);
}
printf("\nWordt omgekeerd:\n");
for (index = 4; index >= 0; index--) {
printf("%c", tekst[index]);
}
fflush(stdin);
getchar();
return 0;
}
Het is niet handig om de karakters in een char array 1 voor 1 te "behandelen".
De programmeertaal C heeft ook de mogelijkheid om de inhoud van een char
array als 1 geheel te "behandelen". Om dit mogelijk te maken moet de tekst
in de array afgesloten worden met een zogenaamd nul-karakter
'\0'
. Een char array waarbij het einde van de tekst aangegeven
wordt door een nul-karakter wordt een C-string genoemd.
Een C-string kan bij de definitie meteen geïnitialiseerd worden:
char tekst[6] = "Hallo";
Let op! De character array moet nu minimaal 6
characters
groot zijn (1 extra voor het afsluitende nul-karakter). Je mag de character
array ook groter maken zonder dat het gedrag van het programma veranderd.
Het afsluitende nul-karakter geeft immers het einde van de tekst aan.
Als we de array meteen initialiseren kan de compiler de grootte van de array ook zelf bepalen:
char tekst[] = "Hallo";
Om een C-string met behulp van printf
af te drukken moet de
format specifier %s
gebruikt worden:
printf("%s", tekst);
Het onderstaande programma cstring.c doet hetzelfde als het eerste voorbeeldprogramma maar behandeld de tekst zoveel mogelijk als 1 geheel.
#include <stdio.h>
int main(void) {
char tekst[] = "Hallo";
int index;
printf("%s", tekst);
printf("\nWordt omgekeerd:\n");
for (index = 4; index >= 0; index--) {
printf("%c", tekst[index]);
}
fflush(stdin);
getchar();
return 0;
}
De mogelijkheden van een C-string zijn echter erg beperkt. Een C-string kan
niet worden toegekend met behulp van =
en niet
worden vergeleken met behulp van ==
, !=
,
<
, <=
, >
en
>=
. Dit komt omdat de naam van een array in C hetzelfde is
als een pointer naar het eerste element van de array. Bij het vergelijken
met behulp van de vergelijkingsoperatoren wordt dus het beginadres van de
array's vergeleken en niet de inhoud van de array's. Deze beperkingen gelden
overigens voor alle array's.
#include <stdio.h>
/* Let op! Dit programma laat zien hoe het NIET moet! */
int main(void) {
char tekst[6];
tekst = "Hallo";
return 0;
}
Het bovenstaande programma cstring_assign.c geeft bij het vertalen met Microsoft Visual Studio de volgende foutmelding:
error: '=' : left operand must be l-value
De naam van een array is namelijk een (constante) pointer naar het eerste
element van de array en dit beginadres van de array kun je niet wijzigen.
Dingen die links van een =
teken mogen staan worden l-values
genoemd (met de l van links) en de naam van een array is dus geen l-value.
Het onderstaande programma cstring_equal.c geeft niet de uitvoer die je mischien zou verwachten.
#include <stdio.h>
/* Let op! Dit programma laat zien hoe het NIET moet! */
int main(void) {
char tekst1[6] = "Hallo";
char tekst2[6] = "Hallo";
if (tekst1 != tekst2) {
printf("DAT IS GEK! %s != %s\n", tekst1, tekst2);
}
fflush(stdin);
getchar();
return 0;
}
De uitvoer van dit programma is:
DAT IS GEK! Hallo != Hallo
Het beginadres van de eerste array is namelijk altijd ongelijk aan het beginadres van de tweede array.
Om de bovengenoemde beperkingen te "omzeilen" zijn een groot aantal standaard
C functies beschikbaar waarmee je C-strings kunt manipuleren. Deze functies
zijn gedeclareerd in de include file string.h
. Een aantal van
deze functies zijn beschreven in de onderstaande tabel:
Voorbeeld | Beschrijving |
---|---|
|
De functie strlen bepaald de lengte van de C-string. Het
afsluitende nul-karakter wordt daarbij niet meegeteld. Het returntype
size_t is een integer type groot genoeg om het resultaat te
kunnen bevatten. De variabele size wordt in dit voorbeeld dus
gelijk aan 5. |
|
De fuctie strcmp vergelijkt de inhoud van twee C-strings
met elkaar. De returnwaarde is 0 als beide C-strings gelijk zijn. De returnwaarde
is >0 als de eerste C-string groter is dan de tweede C-string. Groter
dan betekent in dit geval dat de eerste string bij het op alfabetische volgorde
zetten achter de tweede string komt te staan. De returnwaarde is <0 als
de eerste C-string kleiner is dan de tweede C-string. Kleiner dan betekent
in dit geval dat de eerste string bij het op alfabetische volgorde zetten
voor de tweede string komt te staan. De variabele i wordt in
dit voorbeeld dus <0. |
|
De functie strcpy kopieert de inhoud van de als tweede parameter
meegegeven C-string naar de als eerste parameter meegegeven character array.
Je moet er daarbij goed op letten dat deze character array groot genoeg is.
Vergeet niet om het afsluitende nul-karakter mee te tellen! Zie:
De gevaren van buffer overrun! De inhoud van
de variabele s wordt in dit voorbeeld dus gekopieerd naar de
variabele t . Na afloop bevat t de C-string
"Hallo"
die wordt afgesloten met een nul-karakter. |
|
De functie strncpy is een "veilige" versie van
strcpy . Bij deze functie wordt een derde argument meegegeven
die het aantal characters aangeeft dat in de als eerste parameter meegegeven
character array kan worden weggeschreven. Deze functie beschermt je dus wel
tegen buffer overrun maar het probleem van deze functie is dat de de als
eerste parameter meegegeven character array niet met een nul-karakter wordt
afgesloten als strlen(s) >= sizeof t .
Zie:
http://blog.liw.fi/posts/strncpy/. |
|
De functie strcpy_s is een veilige (secure) versie van strcpy.
Deze functie is opgenomen in de
C11
standaard. De functie
veroorzaakt een run-time assert als de als laatste parameter meegegeven C-string
meer karakters bevat dan het als tweede parameter meegegeven aantal. |
|
De functie strcat "plakt" de inhoud van de als tweede parameter
meegegeven C-string achter de C-string die is opgeslagen in de als eerste
parameter meegegeven character array. Je moet er daarbij goed op letten dat
de character array groot genoeg is. Vergeet niet om het afsluitende nul-karakter
mee te tellen! Zie: De gevaren van buffer
overrun! De inhoud van de variabele s wordt in dit voorbeeld
dus de C-string "Hallo daar" . |
|
De functie strcat_s is een veilige (secure) versie van
strcat . Deze functie is opgenomen in de
C11
standaard. De functie
veroorzaakt een run-time assert als de als de resulterende C-string meer
karakters bevat dan het als tweede parameter meegegeven aantal. In het gegeven
voorbeeld zal dus een run-time assert optreden omdat de resultaat C-string
"Hallo daar" niet past in de character
array s (er is geen ruimte voor het afsluitende nul-karakter).
Zie secure string functies in
C11. |
Een volledig overzicht kun je vinden op: http://en.cppreference.com/w/c/string/byte. Een nog uitgebreidere omschrijving is te vinden op: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/string.h.html.
Je kunt C-strings met behulp van printf
naar het scherm en met
behulp van fprintf
naar een file schrijven. Als format specifier
moet %s
gebruikt worden. C-strings kunnen met scanf
uit het toetsenbord en met fscanf
uit een file gelezen worden.
Als format specifier moet ook %s
gebruikt worden. De functie's
scanf
en fscanf
lezen bij het gebruik van
%s
één woord in (gescheiden door spatie,
TAB of ENTER). Het gebruik van de format specifier %s
bij het
inlezen van een C-string is erg gevaarlijk! Zie: De
gevaren van buffer overrun! Als je zonder gevaar een C-string wilt inlezen
in de array
char woord[20];
Dan kan dat met behulp van de functieaanroep:
scanf("%19s", woord);
De format specifier
%19s
zorgt ervoor dat er nooit meer dan 19 karakters uit het toetsenbordbuffer
worden gelezen. De 20ste plaats in de array woord
kan dan gebruikt
worden voor het afsluitende nul-karakter.
De functies printf
en fprintf
geven een integer
getal terug die het aantal weggeschreven karakters aangeeft. Deze functies
geven een nagatieve waarde terug als er een uitvoerfout optreed. Meer informatie
kun je vinden op:
http://en.cppreference.com/w/c/io/fprintf
en nog meer
op: http://www.opengroup.org/onlinepubs/9699919799/functions/printf.html.
De functies scanf
en fscanf geven een integer getal terug die
het aantal juist ingelezen variabelen aangeeft. Deze functies geven de waarde
EOF terug als geprobeerd wordt om voorbij het einde van de file te lezen
of als er een invoerfout optreed. Meer informatie kun je vinden op:
http://en.cppreference.com/w/c/io/fscanf
en nog meer
op: http://www.opengroup.org/onlinepubs/9699919799/functions/scanf.html.
Gegeven is het volgende programma scanf.c :
#include <stdio.h>
int main(void) {
char woord[20];
printf("Type een aantal strings (sluit af met Ctrl+Z)\n");
while (scanf("%19s", woord) == 1) {
printf("De string: \"%s\" is ingelezen!\n", woord);
}
return 0;
}
Kun je de uitvoer voorspellen als na de onderstaande invoer op de return toets wordt gedrukt?
Klik hier als je niet zeker bent van je antwoord.
De volgende informatie is niet nodig voor MICPRG maar alleen bedoeld voor studenten die meer willen weten.
Je kunt bij het gebruik van scanf
en fscanf
ook
exact opgeven welke karakters zijn toegestaan om in te lezen in een C-string.
Zie: Het inlezen van een string met scanf
of fscanf
inclusief spaties.
Er zijn ook speciale functies voor het lezen en schrijven van C-strings.
De functie puts
kan gebruikt worden om C-strings naar het scherm
te schrijven en fputs
om C-strings naar een file te sturen.
De functie puts
schrijft de als argument meegegeven C-string
naar het beeldscherm gevolgd door een '\n'
karakter. De functie
fputs
schrijft de als eerste argument meegegeven C-string naar
de als tweede parameter meegegeven FILE*
. De functie
fputs
voegt in tegenstelling tot puts
geen
'\n'
karakter toe. De functie gets
kan gebruikt
worden om een C-string van het toetsenbord te lezen maar omdat het maximaal
aantal te lezen karakters niet meegegeven kan worden is het gebruik van deze
functie erg gevaarlijk. Zie: De gevaren van buffer
overrun! De functie fgets
kan gebruikt worden om een C-string
uit een file te lezen. Deze functie heeft 3 parameters. Als eerste moet de
character array waar de C-string wordt opgeslagen worden meegegeven. Als
tweede moet het aantal karakters dat in die karakter array kan worden opgeslagen
worden meegegeven. Tot slot moet een FILE*
worden meegegeven.
Het maximaal aantal karakters dat de functie fgets
uit de file
leest is 1 minder dan het als tweede parameter meegegeven getal. De functie
fgets
geeft NULL
terug als geprobeerd wordt voorbij
het einde van de file te lezen of als er een invoerfout optreed. De functie
fgets
kan ook gebruikt worden om een C-string van het toetsenbord
in te lezen door als derde parameter de voorgedefinieerde
FILE*
stdin
mee te geven.
Gegeven is het volgende programma fgets.c :
#include <stdio.h>
int main(void) {
char woord[20];
printf("Type een aantal strings (sluit af met Ctrl+Z)\n");
while (fgets(woord, 20, stdin) != NULL) {
puts(woord);
}
return 0;
}
Kun je de uitvoer voorspellen als na de onderstaande invoer op de return toets wordt gedrukt?
Klik hier als je niet zeker bent van je antwoord.
Functies die bedoeld zijn om naar een file te schrijven kunnen we ook gebruiken
om naar het beeldscherm te schrijven door de voorgedefinieerde
FILE*
stdout
te gebruiken.
Voorspel de uitvoer (bij ongewijzigde invoer) als in het bovenstaande programma de regel:
puts(woord);
vervangen wordt door:
fputs(woord, stdout);
Klik hier als je niet zeker bent van je antwoord.
Diverse functies zoals strcmp
, strcat
,
gets
, fgets
, scanf
en
fscanf
leveren als resultaat een C-string. Deze C-string wordt
dan opgeslagen in een char
array die door een als parameter
meegegeven char*
wordt aangewezen. Hierbij moet je goed oppassen
voor buffer overrun. Dit probleem treedt op als de C-string meer karakters
bevat als de char
array kan bevatten.
Voorbeeld
:
#include <stdio.h>
#include <string.h>
int checkpassword(void) {
int ok = 0;
char buffer[8];
printf("Adres van buffer = %p, adres van ok = %p\n\n", buffer, &ok);
printf("Geef password: ");
gets(buffer);
if (strcmp("Geheim!", buffer) == 0) {
ok = 1;
}
return ok;
}
int main(void) {
if (checkpassword()) {
printf("Toegang tot geheime informatie.\n");
}
else {
printf("Onjuist password!\n");
}
fflush(stdin);
getchar();
return 0;
}
Je zou denken dat je alleen toegang tot de geheime informatie krijgt als
je het password kent1. Als dit programma
met Microsoft Visual Studio wordt gecompileerd blijkt dat de variabele
ok
16 bytes na de variabele buffer
in het geheugen
wordt geplaatst. Als we meer dan 8 karakters invoeren worden deze karakters
allemaal in het geheugen weggeschreven (buffer overrun). Op deze manier kan
ook de variabele ok
overschreven worden.
Buffer overrun kun je in dit geval eenvoudig voorkomen door in plaats van
de functies gets
de functie fgets
te gebruiken.
In het bovenstaande programma moet de regel:
gets(buffer);
worden vervangen door:
fgets(buffer, 8, stdin);
De functie zal nu hooguit 7 karakters inlezen (en de achtste plaats in de buffer vullen met het nul karakter).
Het is overigens nog beter om:
fgets(buffer, sizeof buffer, stdin);
te gebruiken. Nu kan de grootte van de buffer zonder verdere consequenties gewijzigd worden.
1 Ik ben er wel vanuit gegaan dat de gebruiker
alleen uitvoerrechten heeft voor het programma en geen leesrechten! Anders
zou de gebruiker namelijk eenvoudig het geheime password kunnen opsporen
door de bufferoverrun2.exe
file in notepad te laden. Als de
gebruiker ook leesrechten heeft op de executable moeten we het password
"encrypted" in de exe file opslaan.
Een ander (soortgelijk) voorbeeld van buffer overrun kun je hier vinden.
In de C11 standaard zijn veilige versies van verschillende string functies opgenomen. Deze functies zijn bedoeld om buffer overrun te voorkomen. In Microsoft Visual Studio kun je gebruik maken van deze functies (zie: http://msdn.microsoft.com/en-us/library/8ef0s5kh%28v=vs.100%29.aspx).
Voorbeeld strcat_s.c :
#include <stdio.h>
#include <string.h>
int main(void) {
char s[10] = "Hallo";
strcat_s(s, sizeof s, " daar");
printf("%s\n", s);
getchar();
return 0;
}
Als dit programma uitgevoerd wordt in de debugger van Microsoft Visual Studio dan verschijnt de volgende run-time assert:
Als dit programma gecompileerd wordt in Release mode (in tegenstelling tot Debug mode) en uitgevoerd wordt buiten de debugger dan wordt het programma afgesloten met de volgende foutmelding:
Dit komt omdat de resultaat C-string "Hallo
daar"
niet past in de character array s
(er is
geen ruimte voor het afsluitende nul-karakter). Om dit probleem op te lossen
moet de regel:
char s[10] = "Hallo";
vervangen worden door:
char s[11] = "Hallo";
Het gebruik van de functie strcat_s
zorgt er dus voor dat het
programma wordt afgesloten zodra er een buffer overrun optreed.
Als in het bovenstaande programma gebruik gemaakt wordt van
strcat
in plaats van strcat_s
dan lijkt het programma
correct te werken terwijl er sprake is van een buffer overrun Zie:
De gevaren van buffer overrun! Als het programma
in de Release mode gecompileerd en uitgevoerd wordt dan merk je helemaal
niets van deze buffer overrun. Als het programma in Debug mode gecompileerrd
en uitgevoerd wordt in Visual Studio dan verschijnt bij het afsluiten van
het programma nog wel de foutmelding:
Maar dat komt als mosterd naar de maaltijd omdat het programma gewoon verder gaat nadat de buffer overrun is opgetreden.