Transcript Část C

David Bednárek
Filip Zavoral

Vývoj
◦
◦
◦
◦
◦
◦
◦
1970-3
1978
1983
1985
1998
1999
2011
první verze C, společný vývoj s UNIXem, jádro v C
Kerninghan, Ritchie: The C Programming Language
Rick Mascitti: poprvé název C++
Stroustrup: The C++ Programming Language
ISO/ANSI norma C++
ISO/ANSI norma C - 'C99'
C++11
 zásadní rozšíření jazyka - lambda, r-values, generic prog., ...

Proč C v 21. století
◦ jádra OS, drivery, pračky, knihovny,
◦ údržba sw
... neboli co v C není

OOP
◦ classes, inheritance, member functions, constructors and destructors,
virtual functions, access control, pointers to members, static members

Syntax
◦ templates, exceptions, references, overloading, default arguments,
namespaces, new/delete, casting, friend, inline, RTTI, auto, lambda,
rvalues, ..., ..., ...

Libraries
◦ containers, iterators & algorithms
◦ strings
◦ streams

bool
bool b = true;
_Bool b = TRUE;


C99
typedef enum bool_ { FALSE, TRUE} bool;
bool b = TRUE;
int b2 = a > 1;
struct & enum tags
struct S {
int x;
};
struct S {
int x;
};
typedef struct S_ {
int x;
} S;
S s;
struct S s;
S s;
implicitní konverze - enum, void *
enum E { NULA };
E e = NULA;
int x = (int)e;
int* p = (int*)malloc(sizeof(int));

enum E { NULA };
enum E e = NULA;
int x = e;
int* p = malloc(sizeof( int));
main - return 0;
int main()
{ ...
}
int main()
{ ...
return 0;
}

Každý řetězec
musí být vždy
ukončen nulou
Řetězec - pole znaků (char) zakončené nulou
◦ konvence, knihovny
"Ahoj"
'A'
'h'
'o'
'j'
'\0'
'\0' = 0
char buffer[4];
strcpy( buffer, "Ahoj");
'A'
pozor na uvozovky a apostrofy !



X
'X'
"X"
'h'
'o'
'j'
'\0'
vždy myslet na koncovou nulu !
proměnná
znaková konstanta - celočíselná hodnota
řetězec - ukazatel
kód znaku v použitém
kódování
(ASCII, CP1250,
ISO8859-2, EBCDIC, ...)
Inicializované pole
(konstantní ukazatel)
++s1 nelze!


... = { 'U', 'n', 'o', 0 };
static char s1[] = "Uno";
const char *s2 = "Due";
Inicializovaná proměnná typu ukazatel
s2++ se přesune na další znak

puts( "Uno");
s1:
'U'
'n'
anonymní globální proměnná
const char[]
'o'
'\0'
s2:
'D'
'u'
'e'
ekvivalent globální proměnné typu const char[ ]
inicializované obsahem konstanty
'\0'
int strlen ( const char* s)
{
int i = 0;
while ( s[i] != '\0') {
++i;
}
return i;
}
int i = 0;
while ( *s != '\0') {
++i;
++s;
}
return i;
přístup přes ukazatel
více inkrementací
prázdné tělo
nezapomenout na ';' !!
for( i=0; *s != '\0'; ++i)
++s;
for( i=0; *s != '\0'; ++i, ++s) ;
složitější podmínka:
test nenulovosti
inkrementace ukazatele
int i=0;
while ( *s++ != '\0')
++i;
while(a!=0)  while(a)
podmínka je splněna
pokud je nenulová
int i=0;
while ( *s++)
++i;
rozdíl ukazatelů =
počet prvků mezi nimi
pozor na ± 1 !
char *p = s;
while (*p++) ;
return p-s-1;
vždy pozor na dostatek místa
funkce nic nekontroluje !!!

char buf[6];
char pozdrav[] = "Dobry den";
strcpy( buf, pozdrav);
buf
pozdrav
váš program provedl...
D
o
b
r
y
d
e
n
\0
D
o
b
r
y
d
e
n
\0
kopírování na neinicializovaný ukazatel !!!
váš program provedl...
char *ptr;
char pozdrav[] = "Ahoj";
strcpy( ptr, pozdrav);
ptr neinicializovaný !!!
ptr
?
A
h
o
j
\0
A
h
o
j
\0
pozdrav
string cele_jmeno( const string& jm, const string& prijm)
{
return jm + " " + prijm;
}
char * cele_jmeno( const char * jm, const char * prijm)
{
char buf[ 100];
strcpy( buf, jm);
strcat( buf, " ");
strcat( buf, prijm);
return buf;
}

Naprosto chybné řešení
◦ Nekontroluje přetečení pole buf
◦ Vrací odkaz na lokální proměnnou, která v okamžiku návratu zaniká
char * cele_jmeno( const char * jm, const char * prijm)
{
static char buf[ 100];
strcpy( buf, jm);
strcat( buf, " ");
strcat( buf, prijm);
return buf;
}

Chybné řešení
◦ Nekontroluje přetečení pole buf
◦ Používá statickou proměnnou
 zbytečně zabírá místo i v době, kdy funkce není vyvolána
 opakovaná volání ji sdílejí:
if ( strcmp( cele_jmeno( j1, p1), cele_jmeno( j2, p2)) )
 podmínka nikdy nebude splněna, protože strcmp vždy dostane
stejné ukazatele na totéž pole buf
void cele_jmeno( char * buf, const char * jm, const char * prijm)
{
strcpy( buf, jm);
strcat( buf, " ");
strcat( buf, prijm);
}

Funkční řešení, ale nebezpečné
◦ Nekontroluje přetečení pole buf
◦ Pokud volající nemá spolehlivý horní odhad velikostí jména a příjmení,
nemůže tuto funkci bezpečně volat
◦ Většina C knihoven
ale funguje podobně
void tisk( const char * jm, const char * prijm)
{
char buf[ 100];
cele_jmeno( buf, jm, prijm);
puts( buf);
}
int cele_jmeno( char * buf, size_t bufsize,
const char * jm,
const char * prijm)
{
size_t lj = strlen( jm);
size_t lp = strlen( prijm);
if ( lj + lp + 2 > bufsize )
{ /* error */ return -1; }
memcpy( buf, jm, lj);
buf[ lj] = ' ';
memcpy( buf + lj + 1, prijm, lp);
buf[ lj + lp + 1] = 0;
return lj + lp + 1;
}
max velikost pole
kontrola korektnosti
výsledku
kontrola velikosti pole
pozor na mezeru a konec!
kopírování jednotlivých částí
návrat výsledné délky
void tisk( const char * jm, const char * prijm)
{ enum { N = 100 };
char buf[ N];
if( cele_jmeno( buf, N, jm, prijm) > 0)
puts( buf);
}
A * x[10]
pole ukazatelů
A (* x)[10]
ukazatel na pole
A * x()
funkce vracející ukazatel
A (* x)()
ukazatel na funkci
A x[10]()
pole funkcí - zakázáno
A (* x[10])()
pole ukazatelů na funkci
A x()[10]
funkce vracející pole - zakázáno
A (* x())[10]
funkce vracející ukazatel na pole
typicky se nepoužívá
pole = ukazatel na 1. prvek
čtení deklarací:
od identifikátoru doprava, až to nepůjde, tak doleva
int*(*pf[10])(void);
int*(*maso(int*(*p1)(void),int*(*p2)(void)))(void);
co to je za maso ???
int*(*pf[10])(void);
int*(*maso(int*(*p1)(void),int*(*p2)(void)))(void);
typedef int* fce( void);
fce * pf [10];
fce * maso( fce* p1, fce* p2);
použitím typedef
se výrazně zpřehlední kód
class A {
public:
A() : x_(1) {}
int f( int y)
{ return x_ + y; }
private:
int x_;
};
A* a = new A;
a->f(2);
explicitní
this
absence
ochrany
absence
konstruktoru
a destruktoru
nemožnost
přetížení
explicitní
inicializace
typedef struct A_ {
int x_;
} A;
int A_init( A* a) {
a->x_ = 1; }
int A_f( A* a, int y) {
return a->x_ + y; }
A* a = malloc( sizeof( A));
A_init(a);
A_f(a,2);
explicitní prefix nebo
možnost kolize
beztypová
alokace
class A {
virtual int f(void)
{ return 1; }
};
class B : public A {
virtual int f(void)
{ return 2; }
}
A* a = new B;
a->f();
virtual
method
ruční
inicializace
VMT
typedef int fnc(void);
typedef struct A_ {
fnc* f_;
} A;
void A_init( A* a, fnc*
int A_f( A* a) { return
int A_f_body() { return
int B_f_body() { return
konstruktor
inicializace VMT
f) { a->f_ = f; }
(*a->f_)(); }
1; }
2; }
A* a = malloc( sizeof( A));
A_init( a, B_f_body);
A_f( a);
zavolá
se B_f
'odvozená
metoda'
#include <stdarg.h>
typedef va_list ????
va_start( va_list argptr, last_parm);
va_arg( va_list argptr, type);
va_end( va_list argptr );
speciální typ
inicializace
přístup
ukončení
int sum( int num, ...) {
int rv = 0;
va_list argptr;
va_start( argptr, num);
for( ; num > 0; num--) {
rv += va_arg( argptr, int);
}
va_end( argptr);
return rv;
}
int answer = sum( 4, 4, 3, 2, 1);
typ !
opravdu '...'
korektnost
parametrů musí
zajistit uživatel
funkce
Náhrada v C++: Variadic Templates
pole řetězců
(ukazatelů na char)
C:\> myprog.exe -n -w a.txt b.txt
int main( int argc, char** argv)
argv
0
argc 5
a
Počet parametrů
včetně názvu programu !
= počet ukazatelů v argv
.
t
x
.
t
x
t
\0
.
e
- w \0
m y
vector<string> arg( argv, argv+argc);
b
n
p
\0
r
o g
x
e
\0
t
\0
usage: myprog [-n] [-w] fileA fileB
int main( int argc, char** argv)
{ int n=0, w=0;
while( *++argv && **argv=='-')
{ switch( argv[0][1]) {
case 'n': n = 1; break;
case 'w': w = 1; break;
default: error();
}
}
if( !argv[0] || !argv[1])
error();
doit( argv[0], argv[1], n, w);
return 0;
}
options
nastavení
přepínače
zbývající
parametry
0
b
.
t
x
t
\0
a
.
t
x
t
\0
.
e
x
- w \0
výkonná funkce
argv
-
n
\0
p
r
g
e
\0
int main( int argc, char** argv)
{ int n=0, w=0;
int x = 0;
char* f = 0;
while( *++argv && **argv=='-')
{ switch( argv[0][1]) {
case 'n': n = 1; break;
case 'w': w = 1; break;
case 'x': x = atoi( argv[0]+2); break;
case 'f': f = argv[0]+2; break;
default: error();
}
}
if( !argv[0] || !argv[1]) error();
doit( argv[0], argv[1], n, w, x, f);
return 0;
}
argv
usage: myprog [-n] [-w]
[-x123] [-ffilename]
fileA fileB
číselný
parametr
řetězcový
parametr
-
f
f
i
l
...
-
x
1 2 3
\0
int main( int argc, char** argv)
{ int n=0, w=0;
int x = 0;
char* f = 0;
while( *++argv && **argv=='-')
{ switch( argv[0][1]) {
case 'n': n = 1; break;
case 'w': w = 1; break;
case 'x': x = atoi( argv[0]+2); break;
case 'f': if( argv[0][2]) f = argv[0]+2;
else f = *++argv;
break;
default: error();
}
}
if( !argv[0] || !argv[1]) error();
doit( argv[0], argv[1], n, w, x, f);
return 0;
}
argv
usage: myprog [-n] [-w]
[-x123] [-f filename]
fileA fileB
-ffile
-f file
f
i
l
-
f
\0
-
f
f
e n
...
i
...
l
int printf( const char * format, ...);
◦
◦
◦
◦
◦
◦
◦
◦
% [flags] [width] [.precision] [opt_pref] type
%c - char - znak
%d - int - decimálně
místo width a/nebo precision znak *
hodnota následujícího parametru
%x - int - šestnáctkově
%ld - long
%f - double - s desetinnou tečkou
width: min. počet míst na výstupu
%g - double - s exponentem
precision: max. počet zpracovávaných znaků
%s - char * - řetězec
#include <stdio.h>
printf( "Ahoj %s dnes je %d.%d.", "Babi", 8, 1);
shodu formátu a parametrů musí zajistit programátor
neshoda: nedefinované chování 
fprintf( FILE*,
sprintf( char*,
swprintf( wchar_t*,
_snprintf( char*, int n,
_scprintf(
...
printf(":%c:%6d:%08X:%7.3f:%7.3f:\n",
'a', 17, 1234, -1234.56, -1234.5678);
:a:▫▫▫▫17:000004D2
:▫▫-1234.560:▫▫-1234.568:
printf(":%s:%6s:%6s:%-6s:%-6.3s:%.3s:\n",
"ahoj", "ahoj", "ahojbabi",
"ahoj", "ahoj", "ahoj");
:ahoj:▫▫ahoj:ahojbabi
:ahoj▫▫:aho▫▫▫:aho:
printf(":%*d:%-*.*s:\n",
6, 17, 6, 3, "ahoj");
:▫▫▫▫17:aho▫▫▫:
printf(":%c:%c:%d:%s:\n",
0xffeedd41, "ahoj", "ahoj", 'a');
:A::78263451:●
Unhandled exception at 0x0041470c :
Access violation reading location 0x00000061
typ 'soubor'
(ukazatel na strukturu)
otevření souboru
konvence dle OS
#include <stdio.h>
FILE* fp;
int c;
pozor na '\\' !!!
if( !(fp = fopen("c:\\f.txt", "r")))
error();
while( (c = getc( fp)) != EOF)
putchar( c);
fclose( fp);
w: soubor se smaže
r: soubor musí
existovat
mode
soubor ex. soubor neex.
seek
r
R
Error
0
w
Del, W
W
0
a
W
W
End
r+
R/W
Error
0
w+
Del, R/W
R/W
0
a+
R/W
R/W
End
a: otevřít na konci
konstanta v stdio.h
různá od všech
možných dat
+: vždy čtení i zápis

Textový soubor
◦
◦
◦
◦
◦
◦

konverze konců řádek ('\n') na platformově závislou vnější reprezentaci
typicky 0x0D 0x0A (Win) nebo 0x0A (Unix)
konverze je automatická, programátor se o to nemusí starat
vhodné pro ukládání lidsky čitelných dat
getc/putc, fgets/fputs, fprintf, ...
chování fseek/ftell na '\n' nedefinován - nepoužívat
Binární soubor
◦
◦
◦
◦
◦
◦
žádné konverze se neprovádí
v souboru je přesný binární obraz zapisovaných dat
vhodné pro ukládání vnitřních datových struktur
lidsky přímo nečitelné
typicky fread/fwrite, lze i getc/putc (přístup po bajtech)
fseek/ftell OK
FILE* fopen( const char* fname, const char* mode);
int fclose( FILE* fp);
int fprintf( FILE* fp, const char* format, ...);
int getc( FILE* fp);
int putc( int c, FILE* fp);
char* fgets( char* buffer, size_t limit, FILE* fp);
int fputs( const char* buffer, FILE* fp);
size_t fread( void* ptr, size_t size, size_t n, FILE* fp);
size_t fwrite( const void* ptr, size_t size, size_t n, FILE* fp);
long ftell( FILE* fp);
int fseek( FILE* fp, long offset, int whence);
whence: SEEK_SET, SEEK_CUR, SEEK_END
int fflush( FILE *fp);
funkce pro práci se standardním vstupem/výstupem
int getchar( void);
int putchar( int c);
int printf( const char* format, ...);
char* gets( char* buffer);
Nikdy nepoužívat!
Nelze ohlídat přetečení bufferu
standardní vstup/výstup
FILE* stand. otevřený na čtení/zápis před vstupem do main
FILE *stdin;
FILE *stdout;
getchar()  getc(stdin)
putchar(c)  putc(c, stdout)
všechny souborové
funkce lze použít
i pro std. v/v
FILE* fp = stdout;
if( ...) fp = fopen( "...", "r");
c = getc( fp);
jednotný zápis
na std výstup nebo
do souboru
<string.h> <cstring>
C++ namespace std
strlen, strcmp, stricmp, strcpy, strncpy, strcat, strchr, strrchr, strstr,
memset, memcmp, memcpy, memchr
<stdio.h> <cstdio>
getchar, putchar, fopen, tmpfile, fclose, getc, putc, fgets, fputs, fread
fwrite, ftell, fseek, printf, fprintf, vfprintf, fflush, ungetc
FILE, stdin, stdout, EOF, SEEK_SET, ...
<stdlib.h> <cstdlib>
malloc, free, atoi, atof, strtol, qsort, rand, exit
<ctype.h> <cctype>
isalpha, isdigit, isxdigit, isalnum, isspace, ispunct, iscntrl, islower,
isupper, tolower, toupper
<math.h> <cmath>
abs, floor, sin, sqrt, exp, exp, log, ...
<time.h> <ctime>
time, gmtime, strftime, asctime, clock, ...
... a mnoho dalších
<assert.h> <errno.h>
<limits.h> <locale.h>
<stdarg.h> <setjmp.h>
#define _ F-->00||F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
Je v C, nikoliv v C++

vlastní C moduly
◦ obj, lib, dll/so
◦ jak linkovat C a C++ moduly
◦ jak dělat společné C/C++ headery

cizí C knihovny
◦
◦
◦
◦
jak z C++ volat C knihovny
callback z C knihoven do C++
mandlování, volací konvence
dynamicky linkované knihovny
vlastní headery
.h
.obj
.obj
.obj
.h
.cpp
knihovny
knihovní headery
CC
.obj
kompilace jednoho modulu
.obj
.obj
.obj
.c
.c
.cpp
další moduly
.obj
.obj
.lib
Link
.exe
vlastní headery
.h
knihovní headery
.h
.cpp
CC
.obj
kompilace jednoho modulu
.obj
.obj
.obj
.c
.c
.cpp
další moduly
Lib
.lib
zdrojový text /
překladač C
lib.c
CC
.obj
Lib
.lib
CPPC
.obj
Link
.exe
lib.h
exe.cpp
zdrojový text /
překladač C++
lib.c
CC
.obj
Lib
.lib
Link
.exe
lib.h
exe.cpp

CPPC
.obj
error LNK2019: unresolved external symbol
◦ "int __cdecl lib_fnc(int)" (?lib_fnc@@YAHH@Z)
◦ referenced in function _main
what the ... ... hell???

mangling
◦ mandlování
◦ znetvoření
◦ name-decoration

syntaktická a sémantická informace o symbolu
◦
◦
◦
◦
◦

int a;
int a( void);
int a( int, int);
class a {};
class a { int a; };
class a { int a( int); };
zjednoznačnění identifikátoru
proměnná / funkce / operator / metoda
typy a typové konstrukce parametrů a návratové hodnoty
třída, další atributy (const, volatile, ...)
volací konvence
formát jednotně nedefinovaný
◦ závislý na platformě, překladači, ...
◦ obecně nepřenositelné
/* pureclib.h */
/* pureclib.c */
#include "pureclib.h"
#ifndef PURECLIB__H_
#define PURECLIB__H_
// cppexe.cpp
#include "pureclib.h"
int lib_x;
extern int lib_x;
int lib_fnc( int x);
int main(....)
{
int i = lib_fnc( 1);
}
int lib_fnc( int x)
{
return old_x;
}
CC
_lib_fnc
#endif
různé překladače
různé jazyky
různá implementace
CPPC
?lib_fnc@@YAHH@Z
/* pureclib.h */
symboly C
/* pureclib.c */
#include "pureclib.h"
#ifndef PURECLIB__H_
#define PURECLIB__H_
// cppexe.cpp
#include "pureclib.h"
int lib_x;
#ifdef __cplusplus
extern "C" {
#endif
int main(....)
{
int i = lib_fnc( 1);
}
int lib_fnc( int x)
{
return old_x;
}
extern int lib_x;
int lib_fnc( int x);
#ifdef
}
#endif
CC
_lib_fnc
__cplusplus
#endif
CPPC - definované
CC - nedefinované
CPPC
_lib_fnc

způsob implementace volání funkcí
◦
◦
◦
◦
◦

registry vs. zásobník
zachovávání registrů
pořadí předávání parametrů
návratová hodnota
příprava a úklid zásobníku
f( 1, 2);
mov eax, 1
mov eax, [ebp+08]
mov ebx, 2
mov ebx, [ebp+04]
call ?f@@X
...
konkrétní konvence
◦ není součástí normy - rozšíření
 __cdecl
- default for C and C++, varargs
 __stdcall
- Win32 API functions
 __fastcall
- arguments in registers, faster
 __thiscall
- this
 __clrcall
- C++/CLI, .Net, managed code
callback
knihovní kód volá
klientskou funkci
/* pureclib.h */
#ifdef __cplusplus
extern "C" {
#endif
extern "C" určuje i
volací konvenci
int lib_cb( int x, int (*cb_fnc)( int));
#ifdef
}
#endif
__cplusplus
// cppexe.cpp
#include "pureclib.h"
/* pureclib.c */
#include "pureclib.h"
int lib_cb( int x, int (*cb_fnc)( int))
{
return cb_fnc( x);
}
CC očekává C funkci
extern "C" int cpp_fnc( int x) {
return x+1;
}
int main() {
lib_cb( i, cpp_fnc);
}


použití funkcí dodaných až za běhu
není součástí normy
◦ použití na různých platformách ideově podobné
 ale nepřenositelné
◦ pomocí preprocesoru lze multiplatformní rozhraní
 ale netriviální

Windows
◦ .dll
◦ chová se jako .exe
◦ vlastní zásobník, heap, standardní knihovny

Linux / Unix
◦ .so
◦ chová se jako .lib
◦ balíček .o
more details:
http://www.symantec.com/connect/articles/
dynamic-linking-linux-and-windows-part-one
...-part-two
// dll.cpp
// exe_explicit.cpp
extern "C" __declspec(dllexport)
int add( int a, int b) {
return a + b;
}
HINSTANCE dll =
LoadLibrary( TEXT("dll.dll"));
if( dll == NULL)
return 1;
BOOL APIENTRY DllMain(....) {
return TRUE;
}
typedef int dll_fnc(int, int);
dll_fnc* add = (dll_fnc*)
GetProcAddress( dll, "add");
if( add == NULL) {
FreeLibrary( dll);
return 1;
}
load dll
explicit runtime linking
int result = add(1, 2);
běžné volání
FreeLibrary( dll);
statické slinkování
s dll.lib
// exe_import.cpp
jen proxy, kód v .dll
extern "C" __declspec(dllimport)
int add(int a, int b);
int result = add(1, 2);