<< 1. Praktikum | NASM Lehele | 3. Praktikum >>

C – Praktikum2: Lihtsamad programmid, koodipunktid, sisendi lugemine

Selles praktikumis kirjutame lihtsaid testprogramme, mis tutvustavad C keele kõige lihtsamat taset. Töökeskkonna seadistamiseks võib vaadata 1. praktikumi materjale.
Selles praktikumis eeldame, et käsurealt ja IDE-s (Visual Studio/DevC++/QtCreator/...) kompileerimine töötab vigadeta. Alusprogramm on järgnev:

// prax2.c  - kompileerimine:  gcc  -std=c99  -Wall  prax2.c  -o prax2.exe
#include <stdio.h>

int main()
{
	printf("prax2\n");

	return 0;
}

Funktsioon printf on C-s standard funktsioon konsooli väljundi kirjutamiseks ja on saadaval päisfailist <stdio.h>. Printf abil saab mugavalt kirjutada erinevaid sõnesid väljundisse stdout:

#include <stdio.h>
int main()
{
	printf("integer = %d\n", 42);
	printf("string  = %s\n", "hello, world");
	return 0;
}

Täitmise ajal on võimalik täisarve ja sõnesid muidugi ka muutujate sisse kirjutada. Kusjuures märkimist väärib, et sõned on C-s lihtsalt char massiivid. C-keeles on massiivide süntaks keerulisem kui teistes keeltes, kuna tegu on fikseeritud mälublokkidega.

#include <stdio.h>
int main()
{
	int i = 42;
	char text[] = "hello, world";
	
	printf("integer = %d\n", i);
	printf("string  = %s\n", text);
	return 0;
}

Lähemalt char text[] süntaksist - nimelt luuakse massiiv, mis on täpselt nii suur, et mahutada sõne "hello, world". Sama tulemuse saame kui kirjutame [sõnepikkus+1] (13) kantsulgude vahele. See loob massiivi, mille suurus on jäigalt ette määratud. Kui sõne ei mahu massiivi, siis saame kompileerimise puhul veateate.

#include <stdio.h>
int main()
{
	int i = 42;
	char text[13] = "hello, world";
	
	printf("integer = %d\n", i);
	printf("string  = %s\n", text);
	return 0;
}

Kui me ei soovi sõnest teha lokaalset koopiat, siis saame võtta viida (pointer) tekstile. Antud juhul paneme ette ka const prefiksi, mis teeb selgeks, et antud teksti ei tohiks üle kirjutada. C string literal on tihti eraldi staatilises mälualas, millele ei tohi kirjutada. Kui kasutame const char* saame veateate kompileerimise ajal. Kui kasutame lihtsalt char*, siis samme käivitamise ajal kriitilise veateate segmentation fault.

#include <stdio.h>
int main()
{
	const char* text = "hello, world";
	printf("string  = %s\n", text);
	
	// text[0] = 'H'; // error: assignment of read-only location '*text'
	
	return 0;
}
#include <stdio.h>
int main()
{
	char* dangerous = "hello, world";
	printf("string  = %s\n", dangerous);
	
	dangerous[0] = 'H'; // runtime error: segmentation fault
	
	return 0;
}


Ülesanne 1: Koodipunktide väljundisse kirjutamine

Kirjutada väljundisse kõikide char-ide [0..255] koodipunkti kümnendsüsteemis ja tema väljund tähemärgina, näiteks:
kood: 64 symb: A

Antud ülesande jaoks on vaja tunda for tsüklit C-keeles. For tsükli kasutamine on üpriski otsekohene, eriti kui on kogemust Java/C# keeltega. Järgnev tsükkel prindib välja arvud [0..9]:

for (int i = 0; i < 10; i++)
	printf("%d ", i);

Ülesanne lahendada iseseisvalt:

#include <stdio.h>
int main()
{
	// iseseisev ülesanne
	
	return 0;
}


Ülesanne 2: Sisendi lugemine ja sõne ümberpööramine (strrev)

Lugeda sisendist stdin sisse sõne, näiteks funktsiooniga gets või fgets. Ning pöörata see kohapeal (in-place) ringi. Näiteks "idiprugat" => "tagurpidi". Sõne pikkust saab mõõta funktsiooniga strlen päisest <string.h>.

Ülesanne lahendada iseseisvalt:

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

void reverse(char* str)
{
	// implement string reverse
}

int main()
{
	char text[128];
	// get user input
	
	// call reverse
	
	// print text to stdout
	
	return 0;
}


Ülesanne 3: Sisendi lugemine ilma ülejooksuta

Olles sisendpuhver nii suur kui tahes, on sisendisse võimalik kirjutada piisavalt palju tähemärke, et puhver jookseb üle. Selline olukord on tuntud kui buffer overflow. Järgnev näide illustreerib probleemi - sisendpuhver on liiga väike ning kasutaja sisend jookseb sellest kindlasti üle:

#include <stdio.h>
int main()
{
	int guard = 42;
	char text[12];
	
	printf("Enter text: ");
	gets(text); // hello, world!!
	
	printf("%d => %s\n", guard, text);
	return 0;
}
Enter text: hello, world!!
8481 => hello, world!!

Väljund pole selline nagu oodatud 42 => hello, world!!, kuna 3 baiti: '!!\0' kirjutati üle puhvri järgmisele mäluväljale, mis antud juhul oli muutuja guard. Kui muutujat poleks, või sisestatud tekst on veel pikem, siis rikub ülejooks ära funktsiooni stacke frame - mis lõpeb segmentation faultiga.

Enter text: hello, world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
555819297 => hello, world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
(segmentation fault)

See on põhjus miks gets funktsiooni kasutada ei tohiks. C standardis pole kahjuks ühest funktsiooni mis rahuldaks kõiki antud tingimusi:

  1. Kui sisend on suurem kui puhvri suurus, siis ülejäänud tähemärke ei kirjutata.
  2. Rea lõputunnust \n tulemusele ei lisata.
  3. Funktsioon tagastab sisseloetud tähemärkide arvu.
Konsulteerides <stdio.h> dokumentatsiooniga, kirjutada funktsioon mis rahuldab eelnevalt nimetatud tingimused ning mille definitsioon on järgnev:

/**
 * @param buffer Destination buffer
 * @param size   Size of the destination buffer
 * @return Number of bytes read
 */
int getline(char* buffer, int size);



Lahendus 1

#include <stdio.h>
int main()
{
	for (int i = 0; i < 256; i++)
		printf("kood: %3d symb: %c\n", i, i);
	return 0;
}


Lahendus 2

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

void reverse(char* str)
{
	char* end = str + strlen(str) - 1;
	
	for (; str < end; ++str, --end)
	{
		char temp = *str;
		*str = *end;
		*end = temp;
	}
}

int main()
{
	char text[128];
	if (gets(text)[0] == '\0')
		return 0;
	
	reverse(text);
	printf("%s\n", text);
	return 0;
}


Lahendus 3

#include <stdio.h>

/**
 * @param buffer Destination buffer
 * @param size   Size of the destination buffer
 * @return Number of bytes read
 */
int getline(char* buffer, int size)
{
	int ch, len = 0, lim = size - 1;

	while (len < lim && (ch = getchar()) != '\n')
		buffer[len++] = ch;
	buffer[len] = '\0';

	if (len == lim) // truncate overflowed chars:
		while (getchar() != '\n');
	return len;
}

int main()
{
	char text[12];
	int len;
	do
	{
		printf("Enter text: ");
		len = getline(text, 12);
		printf("text.len = %d\n", len);
		printf("text.str = \"%s\"\n", text);
	} 
	while (len > 0);

	return 0;
}

<< 1. Praktikum | NASM Lehele | 3. Praktikum >>