%+
... 69.17.9
%-
... 69.17.9
%
...c
69.17.9
%
...d
69.17.9
%
...e
69.17.9
%
...f
69.17.9
%
...g
69.17.9
%
...hd
69.17.9
%
...hhd
69.17.9
%
...hhi
69.17.9
%
...hhn
69.17.9
%
...hho
69.17.9
%
...hhu
69.17.9
%
...hhx
69.17.9
%
...hi
69.17.9
%
...hn
69.17.9
%
...ho
69.17.9
%
...hu
69.17.9
%
...hx
69.17.9
%
...i
69.17.9
%
...lc
69.17.9
%
...ld
69.17.9
%
...Le
69.17.9
%
...Lf
69.17.9
%
...Lg
69.17.9
%
...li
69.17.9
%
...lld
69.17.9
%
...lli
69.17.9
%
...lln
69.17.9
%
...llo
69.17.9
%
...llu
69.17.9
%
...llx
69.17.9
%
...ln
69.17.9
%
...lo
69.17.9
%
...ls
69.17.9
%
...lu
69.17.9
%
...lx
69.17.9
%
...n
69.17.9
%
...o
69.17.9
%
...s
69.17.9
%
...u
69.17.9
%
...x
69.17.9
%0
... 69.17.9
abort()
69.9.7
abs()
69.9.10
and
69.11
and_eq
69.11
asctime()
69.16.5.1
assert()
69.2
assert.h
69.2
atexit()
69.9.7
atof()
69.9.3
atoi()
69.9.3
atol()
69.9.3
atoll()
69.9.3
bitand
69.11
bool
69.12
bsearch()
69.9.9
BUFSIZ
69.17.2
calloc()
69.9.6
CHAR_BIT
69.3
CHAR_MAX
69.3
CHAR_MIN
69.3
clearerr()
69.17.18
clock()
69.16.1
CLOCKS_PER_SEC
69.16.1
clock_t
69.16.1
compl
69.11
ctermid()
69.17.17
ctime()
69.16.5.2
ctype.h
69.7
difftime()
69.16.4.2
div()
69.9.10
div_t
69.9.1 69.9.1
EDOM
69.5
EILSEQ
69.5
EOF
69.17.2
ERANGE
69.5
errno
69.5
errno.h
69.5
exit()
69.9.7
EXIT_FAILURE
69.9.2
EXIT_SUCCESS
69.9.2
false
69.12
fclose()
69.17.7
fdopen()
69.17.7
feof()
69.17.18
ferror()
69.17.18
fflush()
69.17.8
fgetc()
69.17.11
fgetpos()
69.17.14
fgets()
69.17.12
FILE
69.17.1
FILENAME_MAX
69.17.2
flockfile()
69.17.15
fopen()
69.17.7
FOPEN_MAX
69.17.2
fpos_t
69.17.1
fprintf()
69.17.9.1
fputc()
69.17.11
fputs()
69.17.12
fread()
69.17.13
free()
69.9.6
freopen()
69.17.7
fscanf()
69.17.10.1
fseek()
69.17.14
fseeko()
69.17.14
fsetpos()
69.17.14
ftell()
69.17.14
ftello()
69.17.14
ftrylockfile()
69.17.15
funlockfile()
69.17.15
fwrite()
69.17.13
getc()
69.17.11
getchar()
69.17.11
getchar_unlocked()
69.17.15
getc_unlocked()
69.17.15
getenv()
69.9.8
gets()
69.17.12
gmtime()
69.16.4.4
imaxabs()
69.10.3
imaxdiv()
69.10.1
imaxdiv_t
69.10.1
INT16_C()
69.4.2
INT16_MAX
69.4.1
INT16_MIN
69.4.1
int16_t
69.4.1
INT32_C()
69.4.2
INT32_MAX
69.4.1
INT32_MIN
69.4.1
int32_t
69.4.1
INT64_C()
69.4.2
INT64_MAX
69.4.1
INT64_MIN
69.4.1
int64_t
69.4.1
INT8_C()
69.4.2
INT8_MAX
69.4.1
INT8_MIN
69.4.1
int8_t
69.4.1
INTMAX_C()
69.4.5
INTMAX_MAX
69.4.5
INTMAX_MIN
69.4.5
intmax_t
69.4.5
INTPTR_MAX
69.4.4
INTPTR_MIN
69.4.4
intptr_t
69.4.4
inttypes.h
69.10
INT_FAST16_MAX
69.4.3
INT_FAST16_MIN
69.4.3
int_fast16_t
69.4.3
INT_FAST32_MAX
69.4.3
INT_FAST32_MIN
69.4.3
int_fast32_t
69.4.3
INT_FAST64_MAX
69.4.3
INT_FAST64_MIN
69.4.3
int_fast64_t
69.4.3
INT_FAST8_MAX
69.4.3
INT_FAST8_MIN
69.4.3
int_fast8_t
69.4.3
INT_LEAST16_MAX
69.4.2
INT_LEAST16_MIN
69.4.2
int_least16_t
69.4.2
INT_LEAST32_MAX
69.4.2
INT_LEAST32_MIN
69.4.2
int_least32_t
69.4.2
INT_LEAST64_MAX
69.4.2
INT_LEAST64_MIN
69.4.2
int_least64_t
69.4.2
INT_LEAST8_MAX
69.4.2
INT_LEAST8_MIN
69.4.2
int_least8_t
69.4.2
INT_MAX
69.3
INT_MIN
69.3
isalnum()
69.7.1
isalpha()
69.7.1
isascii()
69.7.1
isblank()
69.7.1
iscntrl()
69.7.1
isdigit()
69.7.1
isgraph()
69.7.1
islower()
69.7.1
iso646.h
69.11
isprint()
69.7.1
ispunct()
69.7.1
isspace()
69.7.1
isupper()
69.7.1
isxdigit()
69.7.1
labs()
69.9.10
LC_TIME
69.16.5.3
ldiv()
69.9.10
ldiv_t
69.9.1
limits.h
69.3
llabs()
69.9.10
lldiv()
69.9.10
lldiv_t
69.9.1
LLONG_MAX
69.3
LLONG_MIN
69.3
locale.h
69.6
localtime()
69.16.4.4
LONG_BIT
69.3.2
LONG_MAX
69.3
LONG_MIN
69.3
L_ctermid
69.17.2
L_tmpnam
69.17.2
malloc()
69.9.6
mblen()
69.9.12
mbstowcs()
69.9.14
mbtowc()
69.9.13
MB_CUR_MAX
69.9.2
MB_LEN_MAX
69.3
memccpy()
69.14.1.2
memchr()
69.14.4.1
memcmp()
69.14.3.1
memcpy()
69.14.1.1
memmove()
69.14.1.3
memset()
69.14.5.1
mktime()
69.16.4.3
NDEBUG
69.2
not
69.11
not_eq
69.11
NULL
69.13
offsetof
69.13
or
69.11
or_eq
69.11
pclose()
69.17.16
perror()
69.17.18
popen()
69.17.16
PRId16
69.10.2
PRId32
69.10.2
PRId64
69.10.2
PRId8
69.10.2
PRIdFAST16
69.10.2
PRIdFAST32
69.10.2
PRIdFAST64
69.10.2
PRIdFAST8
69.10.2
PRIdLEAST16
69.10.2
PRIdLEAST32
69.10.2
PRIdLEAST64
69.10.2
PRIdLEAST8
69.10.2
PRIdMAX
69.10.2
PRIdPTR
69.10.2
PRIi16
69.10.2
PRIi32
69.10.2
PRIi64
69.10.2
PRIi8
69.10.2
PRIiFAST16
69.10.2
PRIiFAST32
69.10.2
PRIiFAST64
69.10.2
PRIiFAST8
69.10.2
PRIiLEAST16
69.10.2
PRIiLEAST32
69.10.2
PRIiLEAST64
69.10.2
PRIiLEAST8
69.10.2
PRIiMAX
69.10.2
PRIiPTR
69.10.2
printf()
69.17.9.1
PRIo16
69.10.2
PRIo32
69.10.2
PRIo64
69.10.2
PRIo8
69.10.2
PRIoFAST16
69.10.2
PRIoFAST32
69.10.2
PRIoFAST64
69.10.2
PRIoFAST8
69.10.2
PRIoLEAST16
69.10.2
PRIoLEAST32
69.10.2
PRIoLEAST64
69.10.2
PRIoLEAST8
69.10.2
PRIoMAX
69.10.2
PRIoPTR
69.10.2
PRIu16
69.10.2
PRIu32
69.10.2
PRIu64
69.10.2
PRIu8
69.10.2
PRIuFAST16
69.10.2
PRIuFAST32
69.10.2
PRIuFAST64
69.10.2
PRIuFAST8
69.10.2
PRIuLEAST16
69.10.2
PRIuLEAST32
69.10.2
PRIuLEAST64
69.10.2
PRIuLEAST8
69.10.2
PRIuMAX
69.10.2
PRIuPTR
69.10.2
PRIX16
69.10.2
PRIx16
69.10.2
PRIX32
69.10.2
PRIx32
69.10.2
PRIX64
69.10.2
PRIx64
69.10.2
PRIx8
69.10.2
PRIX8
69.10.2
PRIXFAST16
69.10.2
PRIxFAST16
69.10.2
PRIXFAST32
69.10.2
PRIxFAST32
69.10.2
PRIXFAST64
69.10.2
PRIxFAST64
69.10.2
PRIxFAST8
69.10.2
PRIXFAST8
69.10.2
PRIXLEAST16
69.10.2
PRIxLEAST16
69.10.2
PRIXLEAST32
69.10.2
PRIxLEAST32
69.10.2
PRIXLEAST64
69.10.2
PRIxLEAST64
69.10.2
PRIxLEAST8
69.10.2
PRIXLEAST8
69.10.2
PRIXMAX
69.10.2
PRIxMAX
69.10.2
PRIXPTR
69.10.2
PRIxPTR
69.10.2
PTRDIFF_MAX
69.4.6
PTRDIFF_MIN
69.4.6
ptrdiff_t
69.4.6 69.13
putc()
69.17.11
putchar()
69.17.11
putchar_unlocked()
69.17.15
putc_unlocked()
69.17.15
puts()
69.17.12
P_tmpdir
69.17.2
qsort()
69.9.9
raise()
69.15.6
rand()
69.9.4
RAND_MAX
69.9.2
realloc()
69.9.6
remove()
69.17.5
rename()
69.17.5
rewind()
69.17.14
scanf()
69.17.10.1
SCHAR_MAX
69.3
SCHAR_MIN
69.3
SCNd16
69.10.2
SCNd32
69.10.2
SCNd64
69.10.2
SCNd8
69.10.2
SCNdFAST16
69.10.2
SCNdFAST32
69.10.2
SCNdFAST64
69.10.2
SCNdFAST8
69.10.2
SCNdLEAST16
69.10.2
SCNdLEAST32
69.10.2
SCNdLEAST64
69.10.2
SCNdLEAST8
69.10.2
SCNdMAX
69.10.2
SCNdPTR
69.10.2
SCNi16
69.10.2
SCNi32
69.10.2
SCNi64
69.10.2
SCNi8
69.10.2
SCNiFAST16
69.10.2
SCNiFAST32
69.10.2
SCNiFAST64
69.10.2
SCNiFAST8
69.10.2
SCNiLEAST16
69.10.2
SCNiLEAST32
69.10.2
SCNiLEAST64
69.10.2
SCNiLEAST8
69.10.2
SCNiMAX
69.10.2
SCNiPTR
69.10.2
SCNo16
69.10.2
SCNo32
69.10.2
SCNo64
69.10.2
SCNo8
69.10.2
SCNoFAST16
69.10.2
SCNoFAST32
69.10.2
SCNoFAST64
69.10.2
SCNoFAST8
69.10.2
SCNoLEAST16
69.10.2
SCNoLEAST32
69.10.2
SCNoLEAST64
69.10.2
SCNoLEAST8
69.10.2
SCNoMAX
69.10.2
SCNoPTR
69.10.2
SCNu16
69.10.2
SCNu32
69.10.2
SCNu64
69.10.2
SCNu8
69.10.2
SCNuFAST16
69.10.2
SCNuFAST32
69.10.2
SCNuFAST64
69.10.2
SCNuFAST8
69.10.2
SCNuLEAST16
69.10.2
SCNuLEAST32
69.10.2
SCNuLEAST64
69.10.2
SCNuLEAST8
69.10.2
SCNuMAX
69.10.2
SCNuPTR
69.10.2
SCNx16
69.10.2
SCNx32
69.10.2
SCNx64
69.10.2
SCNx8
69.10.2
SCNxFAST16
69.10.2
SCNxFAST32
69.10.2
SCNxFAST64
69.10.2
SCNxFAST8
69.10.2
SCNxLEAST16
69.10.2
SCNxLEAST32
69.10.2
SCNxLEAST64
69.10.2
SCNxLEAST8
69.10.2
SCNxMAX
69.10.2
SCNxPTR
69.10.2
SEEK_CUR
69.17.2
SEEK_END
69.17.2
SEEK_SET
69.17.2
setbuf()
69.17.8
setvbuf()
69.17.8
SHRT_MAX
69.3
SHRT_MIN
69.3
SIGABRT
69.15.3 69.15.4
SIGALRM
69.15.4
SIGBUS
69.15.4
SIGCHLD
69.15.4
SIGCONT
69.15.4
SIGFPE
69.15.3 69.15.4
SIGHUP
69.15.4
SIGILL
69.15.3 69.15.4
SIGINT
69.15.3 69.15.4
SIGKILL
69.15.4
signal()
69.15.6
signal.h
69.15
SIGPIPE
69.15.4
SIGPOLL
69.15.4
SIGPROF
69.15.4
SIGQUIT
69.15.4
SIGSEGV
69.15.3 69.15.4
SIGSTOP
69.15.4
SIGSYS
69.15.4
SIGTERM
69.15.3 69.15.4
SIGTRAP
69.15.4
SIGTTIN
69.15.4
SIGTTOU
69.15.4
SIGURG
69.15.4
SIGUSR1
69.15.4
SIGUSR2
69.15.4
SIGVTALRM
69.15.4
SIGXCPU
69.15.4
SIGXFSZ
69.15.4
SIG_ATOMIC_MAX
69.4.6
SIG_ATOMIC_MIN
69.4.6
sig_atomic_t
69.4.6 69.15.2
SIG_DFL
69.15.5
SIG_ERR
69.15.5
SIG_IGN
69.15.5
SIZE_MAX
69.4.6
size_t
69.4.6 69.13
snprintf()
69.17.9.1
sprintf()
69.17.9.1
srand()
69.9.4
sscanf()
69.17.10.1
SSIZE_MAX
69.3.2
stdarg.h
69.8
stdbool.h
69.12
stddef.h
69.13
stderr
69.17.4
stdint.h
69.4
stdio
69.17.4
stdio.h
69.17
stdlib.h
69.9
stdout
69.17.4
strcat()
69.14.2.1
strchr()
69.14.4.2
strcmp()
69.14.3.2
strcoll()
69.14.3.3
strcpy()
69.14.1.4
strcspn()
69.14.4.5
strdup()
69.14.1.6
strerror()
69.14.5.2
strerror_r()
69.14.5.3
strftime()
69.16.5.3
string.h
69.14
strlen()
69.14.5.4
strncat()
69.14.2.2
strncmp()
69.14.3.4
strncpy()
69.14.1.5
strpbrk()
69.14.4.6
strrchr()
69.14.4.3
strspn()
69.14.4.4
strstr()
69.14.4.7
strtod()
69.9.3
strtof()
69.9.3
strtoimax()
69.10.4
strtok()
69.14.4.8
strtok_r()
69.14.4.9
strtol()
69.9.3
strtold()
69.9.3
strtoll()
69.9.3
strtouimax()
69.10.4
strtoul()
69.9.3
strtoull()
69.9.3
struct tm
69.16.3
strxfrm()
69.14.3.5
system()
69.9.8
tempnam()
69.17.6
time()
69.16.4.1
time.h
69.16
time_t
69.16.2 69.16.4
tmpfile()
69.17.6
tmpnam()
69.17.6
TMP_MAX
69.17.2
toascii()
69.7.3
tolower()
69.7.3
toupper()
69.7.3
true
69.12
UCHAR_MAX
69.3
UINT16_C()
69.4.2
UINT16_MAX
69.4.1
uint16_t
69.4.1
UINT32_C()
69.4.2
UINT32_MAX
69.4.1
uint32_t
69.4.1
UINT64_C()
69.4.2
UINT64_MAX
69.4.1
uint64_t
69.4.1
UINT8_C()
69.4.2
UINT8_MAX
69.4.1
uint8_t
69.4.1
UINTMAX_C()
69.4.5
UINTMAX_MAX
69.4.5
uintmax_t
69.4.5
UINTPTR_MAX
69.4.4
uintptr_t
69.4.4
UINT_FAST16_MAX
69.4.3
uint_fast16_t
69.4.3
UINT_FAST32_MAX
69.4.3
uint_fast32_t
69.4.3
UINT_FAST64_MAX
69.4.3
uint_fast64_t
69.4.3
UINT_FAST8_MAX
69.4.3
uint_fast8_t
69.4.3
UINT_LEAST16_MAX
69.4.2
uint_least16_t
69.4.2
UINT_LEAST32_MAX
69.4.2
uint_least32_t
69.4.2
UINT_LEAST64_MAX
69.4.2
uint_least64_t
69.4.2
UINT_LEAST8_MAX
69.4.2
uint_least8_t
69.4.2
UINT_MAX
69.3
ULLONG_MAX
69.3
ULONG_MAX
69.3
ungetc()
69.17.11
USHRT_MAX
69.3
va_arg()
69.8
va_copy()
69.8
va_end()
69.8
va_list
69.8
va_start()
69.8
vfprintf()
69.17.9.2
vfscanf()
69.17.10.2
vprintf()
69.17.9.2
vscanf()
69.17.10.2
vsnprintf()
69.17.9.2
vsprintf()
69.17.9.2
vsscanf()
69.17.10.2
WCHAR_MAX
69.4.6
WCHAR_MIN
69.4.6
wchar_t
69.4.6 69.13
wcstoimax()
69.10.4
wcstombs()
69.9.14
wcstouimax()
69.10.4
wctomb()
69.9.13
WINT_MAX
69.4.6
WINT_MIN
69.4.6
wint_t
69.4.6
WORD_BIT
69.3.2
xor
69.11
xor_eq
69.11
_Exit()
69.9.7
_IOFBF
69.17.2
_IOLBF
69.17.2
_IONBF
69.17.2
_POSIX2_...
69.3.2
_POSIX_...
69.3.2
_XOPEN_...
69.3.2
__bool_true_false_are_defined
69.12
__udivdi3()
69.1
__umoddi3()
69.1
Complessivamente, la libreria C è ciò che consente l'uso di funzioni, macroistruzioni e macro-variabili definite dallo standard (ed eventualmente dalle estensioni presenti nel proprio contesto). Generalmente le funzioni vengono fornite già compilate all'interno di una libreria dinamica o statica (per esempio possono essere i file /lib/libc.so
o /usr/lib/libc.a
), ma dal punto di vista formale, la libreria standard è percepita attraverso i file di intestazione.
Per la precisione, lo standard stabilisce che si debba fare riferimento a delle «intestazioni» nel sorgente di un programma scritto in linguaggio C, ma il contesto particolare può essere tale per cui queste potrebbero non esistere fisicamente come ci si attenderebbe da un sistema operativo tradizionale. Anche per questo, nella documentazione standard ci si riferisce solo a intestazioni, senza precisare che debba trattarsi di file. |
In pratica, i file di intestazione, o ciò che ne fa la funzione, sono sempre necessari e al loro interno si dichiarano le macro-variabili, le macroistruzioni e i prototipi delle funzioni, le quali normalmente sono già precompilate in un file separato. A ogni modo, di norma il compilatore è predisposto per utilizzare automaticamente i file precompilati necessari.
Nei capitoli successivi vengono descritti alcuni dei file di intestazione previsti dallo standard del linguaggio, mostrando come potrebbero essere realizzati e, in alcuni casi, anche fornendo una soluzione completa per le funzioni (gli esempi dovrebbero essere disponibili a partire da allegati/c/).
La libreria C viene estesa dallo standard POSIX con componenti aggiuntivi. In alcuni casi, nei capitoli successivi, si fa riferimento anche a estensioni POSIX, con le annotazioni appropriate al riguardo. Va però osservato che per scrivere un programma in linguaggio C, che abbia la massima portabilità fra sistemi operativi molto differenti tra loro, occorre evitare il più possibile le estensioni di qualunque genere. |
Ciò che non si vede negli esempi dei capitoli successivi è la tecnica comune che si usa per evitare di includere ricorsivamente lo stesso file di intestazione più volte: si associa a ogni file una macro-variabile e se all'inizio della lettura questa non risulta dichiarata, il contenuto viene acquisito, altrimenti viene ignorato semplicemente, perché deve essere già stato incluso in precedenza. L'esempio seguente riguarda il file limits.h
:
|
In pratica viene verificato se la macro-variabile _LIMITS_H è già stata definita; se lo è, il contenuto del file viene ignorato. Se invece la macro-variabile non è stata dichiarata, questa allora viene dichiarata e quindi si procede con il lavoro normale del file.
|
Può succedere che il compilatore, per assolvere a funzionalità che figurano essere indipendenti da librerie, debba invece avvalersi di funzioni esterne che non sono previste dallo standard. In particolare, questo problema può verificarsi di fronte alla necessità di svolgere calcoli al di fuori della portata normale del microprocessore.
A titolo di esempio, il compilatore GNU C per la piattaforma x86-32 prevede un tipo intero long long int da 64 bit. Quando si vuole ottenere una divisione intera o il resto di una divisione con variabili di questo tipo, il compilatore GNU C richiama rispettivamente le funzioni __udivdi3() e __umoddi3(). In generale il problema non si avverte, ma se si vuole scrivere la propria libreria C, senza tali funzioni, in pratica non è possibile usare questo tipo intero molto grande.
Si vedano eventualmente i listati della sezione 95.2, relativi a os32, in cui si realizzano queste funzioni con il solo ausilio del linguaggio C.
Il file assert.h
della libreria standard definisce la macroistruzione assert(), da usare per generare informazioni diagnostiche, sulla base dell'esito della valutazione di un'espressione.
La macroistruzione assert() viene definita in due modi alternativi, in base alla presenza o meno della macro-variabile NDEBUG. Per la precisione, in presenza della macro-variabile NDEBUG la macroistruzione assert() deve risultare inerte.
|
La macroistruzione assert() va usata con la sintassi seguente, dove il parametro indica un'espressione di tipo non specificato, purché di tipo scalare:
void assert (espressione); |
Se l'espressione si traduce in un valore Falso, ovvero pari a zero, la macroistruzione emette, attraverso lo standard error, un messaggio contenente l'espressione stessa e altre indicazioni. Precisamente, oltre all'espressione deve apparire: il nome della funzione in cui ci si trova, il nome del file (sorgente) e il numero della riga.
Tuttavia, se la macro-variabile NDEBUG risulta definita, prima dell'inclusione del file assert.h
, la macroistruzione assert() deve essere trasformata dal compilatore come un'istruzione inerte, ovvero l'equivalente di ((void) 0).
Segue un l'esempio di un programma completo in cui si utilizza assert():
|
L'espressione verificata da assert() non può essere vera, pertanto, se non è stata dichiarata la macro-variabile NDEBUG, questo programma dovrebbe produrre un messaggio come quello seguente:
Assertion failed: 123==124, function main, file assert.c, line 5. |
Il file limits.h
della libreria standard definisce delle macro-variabili che riepilogano i limiti dei valori rappresentabili con le variabili scalari intere. Lo standard prescrive dei limiti minimi per la conformità, ma le realizzazioni comuni consentono mediamente di rappresentare valori più grandi (in senso assoluto), a parità di tipo di intero. Infatti, i limiti effettivi dipendono principalmente dalla dimensione della parola del microprocessore e dal modo in cui si rappresentano i valori negativi. Si può osservare che nelle architetture comuni, in cui i valori negativi si rappresentano con il complemento a due, il valore negativo più grande (in senso assoluto) di una variabile è pari a una unità in più rispetto al valore positivo massimo (per esempio il tipo signed char va solitamente da -128 a 127).
L'esempio proposto si riferisce a un'architettura a 32 bit con i valori negativi rappresentati attraverso il complemento a due.
|
|
Eventualmente si veda la realizzazione di questo file nei sorgenti di os32 (listato 95.1.6).
Per avere un'idea di come potrebbero svilupparsi i valori del file limits.h
tra le varie architetture, viene mostrata una tabella in cui si possono paragonare quelli minimi stabiliti dallo standard con quelli usati nei sistemi GNU/Linux con architetture x86-32 e x86-64. Per semplicità si indicano solo i valori senza segno:
|
Per lo standard POSIX, il file limits.h
serve anche per annotare limiti numerici relativi al funzionamento del sistema operativo, come per esempio la quantità massima di file aperti simultaneamente per ogni processo.
Un gruppo di macro-variabili definite nel file limits.h
, caratterizzate per avere il prefisso _POSIX_..., _POSIX2_... e _XOPEN_..., definisce dei limiti minimi di compatibilità con lo standard. Per esempio, la macro-variabile _POSIX_LINK_MAX deve tradursi nel numero 8 e stabilisce che deve essere consentita la creazione di almeno otto collegamenti fisici per ogni file, in qualunque sistema POSIX.
Un secondo gruppo di macro-variabili definite nel file limits.h
, dichiara i limiti massimi effettivi del sistema, riconducibili ai minimi già fissati nel primo gruppo già descritto. Per esempio, la macro-variabile LINK_MAX indica il numero massimo effettivo di collegamenti fisici per file, tenendo conto che deve essere necessariamente maggiore o uguale al valore di _POSIX_LINK_MAX.
Le macro-variabili del secondo gruppo sono facoltative, in quanto i limiti effettivi del sistema, per le varie voci, possono dipendere da fattori dinamici di funzionamento. In ogni caso, devono essere garantiti i valori minimi delle macro-variabili del primo gruppo.
Nell'ambito delle dichiarazioni che fanno già parte dello standard C, va osservato che lo standard POSIX richiede che il byte sia esattamente di 8 bit, pertanto la macro-variabile CHAR_BIT deve tradursi necessariamente nel numero otto. Inoltre, si aggiungono anche qui alcune macro-variabili:
|
|
Il file stdint.h
della libreria standard definisce principalmente dei tipi interi, alternativi a quelli tradizionali, riferiti in modo più diretto al rango. Assieme a questi tipi interi definisce anche delle macro-variabili che consentono di conoscere i limiti esatti di tali tipi, oltre ad altre macro-variabili con i limiti di tipi interi speciali, dichiarati in altri file (si veda eventualmente la realizzazione di questo file nei sorgenti di os32, listato 95.1.13).
Lo standard prescrive che alcuni dei tipi definiti nel file stdint.h
siano opzionali, purché sia rispettato un certo ordine (per esempio, se viene definito un tipo intero con segno, deve essere prevista anche una versione di quel tipo senza segno e viceversa). A tale proposito, le macro-variabili con le quali si possono verificare i limiti, servono anche per consentire al programmatore di verificare la disponibilità o meno del tipo relativo, attraverso istruzioni del precompilatore del tipo #ifdef.
L'esempio proposto si riferisce a un elaboratore x86-32 ed è abbastanza conforme alla configurazione che si può trovare in un sistema GNU/Linux.
Lo standard prescrive un gruppo facoltativo di tipi interi il cui rango è definito precisamente dal nome. Si tratta dei tipi intn_t (con segno) e uintn_t (senza segno), dove n esprime la quantità di bit che compone l'intero. Si tratta necessariamente di tipi facoltativi, perché non è possibile stabilire in modo sicuro che in ogni architettura siano gestibili tipi interi di una data quantità di bit; per esempio, in una certa achitettura «X» potrebbero essere gestiti tipi interi a 8, 16 e 32 bit, mentre in un'architettura «Y» i tipi disponibili effettivamente potrebbero essere a 8, 16, 24 e 32 bit.
Nei sistemi POSIX, questi tipi sono invece obbligatori, costringendo così ad avere byte esattamente di otto bit. |
|
Le macro-variabili usate per definire i limiti di questi valori interi hanno nomi del tipo INTn_MIN, INTn_MAX e UINTn_MAX, per indicare rispettivamente: il valore minimo dei tipi con segno; il valore massimo dei tipi con segno; il valore massimo dei tipi senza segno. Lo standard prescrive precisamente questi valori minimi e massimi, intendendo implicitamente che i valori negativi si rappresentino con il complemento a due:
|
|
Un gruppo richiesto espressamente dallo standard riguarda tipi interi il cui rango sia tale da garantire la rappresentazione di almeno n bit, utilizzando comunque la quantità minima possibile di bit. In questo caso i nomi sono int_leastn_t per i tipi con segno e uint_leastn_t per quelli senza segno. Lo standard prescrive che siano previsti necessariamente i tipi a 8, 16, 32 e 64 bit, mentre ammette che ne siano disponibili anche altri.
|
Le macro-variabili usate per definire i limiti di questi valori interi hanno nomi del tipo INT_LEASTn_MIN, INT_LEASTn_MAX e UINT_LEASTn_MAX, per indicare rispettivamente: il valore minimo dei tipi con segno; il valore massimo dei tipi con segno; il valore massimo dei tipi senza segno. Lo standard attribuisce questi limiti in modo indipendente dalla rappresentazione dei valori negativi, ma tali limiti possono essere estesi in senso assoluto:
|
|
Ai tipi interi di rango minimo sono associate anche delle macroistruzioni, il cui scopo è quello di consentire la rappresentazione corretta dei valori costanti:
INTn_C(valore) |
UINTn_C(valore) |
In pratica, per indicare il valore costante 1 234 567 890, precisando che va inteso come un tipo uint_least64_t, si deve scrivere: UINT64_C(1234567890). Il valore costante in sé, può essere espresso in qualunque modo, purché sia ammissibile nel contesto comune.
|
È evidente che, nel caso dell'esempio mostrato, UINT64_C(1234567890) corrisponde a 1234567890ULL.
Un altro gruppo di tipi richiesti dallo standard è quello il cui rango è tale da consentire la rappresentazione di almeno n bit, utilizzando la quantità minima di bit che garantisce tempi ottimali di elaborazione. In questo caso i nomi sono int_fastn_t per i tipi con segno e uint_fastn_t per quelli senza segno. Lo standard prescrive che siano previsti necessariamente i tipi a 8, 16, 32 e 64 bit, mentre ammette che ne siano disponibili anche altri.
|
Come suggerisce l'esempio, è ragionevole pensare che, dove possibile, il rango usato effettivamente sia quello del tipo intero normale.
Le macro-variabili usate per definire i limiti di questi valori interi hanno nomi del tipo INT_FASTn_MIN, INT_FASTn_MAX e UINT_FASTn_MAX, per indicare rispettivamente: il valore minimo dei tipi con segno; il valore massimo dei tipi con segno; il valore massimo dei tipi senza segno. Lo standard attribuisce questi limiti in modo indipendente dalla rappresentazione dei valori negativi, ma tali limiti possono essere estesi in senso assoluto:
|
|
Sono previsti due tipi opzionali interi, adatti a contenere il valore di un puntatore, garantendo che la conversione da e verso void * avvenga sempre correttamente. Per la precisione si tratta di intptr_t e uintptr_t, dove il primo è un intero con segno, mentre il secondo è senza segno.
|
Le macro-variabili usate per definire i limiti di questi valori interi sono INTPTR_MIN, INTPTR_MAX e UINTPTR_MAX, per indicare rispettivamente: il valore minimo con segno, il valore massimo con segno e il valore massimo senza segno. Lo standard attribuisce dei limiti riferiti ad architetture in grado di indirizzare al massimo con 16 bit e ovviamente vanno adattati alla realtà dell'architettura effettiva:
|
|
Per poter rappresentare in modo indipendente dall'architettura degli interi di rango massimo, sono previsti due tipi specifici, richiesti espressamente dallo standard: intmax_t e uintmax_t. Le macro-variabili che definiscono i limiti sono: INTMAX_MIN, INTMAX_MAX e UINTMAX_MAX. Lo standard prescrive che si tratti di variabili con un rango di almeno 64 bit.
|
|
Anche ai tipi interi di rango massimo sono associate delle macroistruzioni per facilitare la rappresentazione corretta dei valori costanti:
INTMAX_C(valore) |
UINTMAX_C(valore) |
In pratica, per indicare il valore costante 1 234 567 890, precisando che va inteso come un tipo uintmax_t, si deve scrivere: UINTMAX_C(1234567890). Il valore costante in sé, può essere espresso in qualunque modo, purché sia ammissibile nel contesto comune.
|
A questo punto, anche la definizione dei valori minimi e massimi diventa più agevole:
|
Altri tipi interi dichiarati al di fuori del file stdint.h
hanno i limiti definiti qui. Ne viene mostrata solo una tabella riepilogativa.
|
L'esempio seguente riporta i valori usati in un sistema GNU/Linux con architettura x86-32, a parte il caso di wchar_t e wint_t che si intendono rispettivamente a 32 bit senza segno e 64 bit con segno:
|
Il file errno.h
della libreria standard definisce principalmente delle macro-variabili per rappresentare simbolicamente delle situazioni di errore. Queste macro-variabili si espandono in un numero intero e positivo, di tipo int, ma la corrispondenza tra l'errore simbolico rappresentato dalla macro-variabile e il numero in cui questa si deve espandere, dipende dalle convenzioni del sistema operativo (si veda eventualmente la realizzazione del file errno.h
nei sorgenti di os32, sezione 95.5, tenendo conto che lì si aggiungono delle funzioni non standard, con le quali è più facile l'individuazione della posizione del sorgente in cui l'errore si è manifestato).
Lo standard del linguaggio prescrive poche macro-variabili, da cui dipendono le librerie standard, mentre tutte le altre sono competenza delle convenzioni del sistema operativo.
Oltre alle macro-variabili che rappresentano le situazioni di errore previste, il file errno.h
deve dichiarare errno, in qualità di espressione che si traduca in una variabile scalare. In pratica può trattarsi di una variabile esterna o di una macro-variabile che si traduce in qualunque cosa consenta di assegnarvi un valore. Il valore iniziale che si può leggere da errno è zero (con cui si intende l'assenza di qualunque tipo di situazione di errore) e viene modificato dalle funzioni che, di volta in volta, possono avere bisogno di annotare uno stato di errore.
Si osservi che i numeri che si vedono associati alle macro-variabili sono stati tratti, come esempio, dalla configurazione di un sistema GNU/Linux.
|
Il nome errno, in un sistema che consenta l'esecuzione di programmi suddivisi in più thread, deve tradursi in un'espressione tale da rappresentare una variabile scalare individuale per ogni thread, in modo che i thread non possano interferire tra di loro a questo proposito. Evidentemente, l'esempio mostrato non offre questa accortezza.
|
Il file di intestazione errno.h
di un sistema POSIX è più articolato, in quanto contiene un elenco numeroso di macro-variabili. Valgono naturalmente le stesse considerazioni per la variabile globale errno, a proposito dei thread multipli. Nell'esempio successivo, i numeri associati alle varie macro-variabili non fanno riferimento ad alcun sistema operativo reale; va osservato inoltre che ogni sistema POSIX aggiunge propri tipi di errore, necessari per le proprie caratteristiche specifiche.
|
Il file locale.h
della libreria standard definisce delle macro-variabili, un tipo di struttura e alcune funzioni, relative alla gestione della configurazione locale del programma. Se non si fa uso di tale configurazione, il proprio programma opera in quella che è nota come «configurazione locale C», ovvero il minimo indispensabile.
Nell'ambito di un sistema operativo Unix o simile, la configurazione locale avviene principalmente attraverso l'impostazione di variabili di ambiente il cui nome inizia per LC_.... Tuttavia, questa configurazione non viene ereditata automaticamente dal programma scritto in linguaggio C, perché questo deve acquisirla espressamente, se vuole.
La definizione della configurazione locale avviene attraverso una stringa contenente delle sigle che esprimono la lingua, la nazionalità e la codifica da utilizzare per rappresentare i caratteri. Per esempio, it_CH.UTF-8 rappresenta la lingua italiana, la nazionalità svizzera e la codifica UTF-8.
Per modificare l'impostazione della configurazione locale del proprio programma, si utilizza la funzione setlocale() che richiede l'indicazione di un numero intero, a rappresentare la categoria nella quale intervenire. La categoria viene definita formalmente attraverso delle macro-variabili il cui nome inizia per LC_... e si tratta degli stessi nomi usati nelle variabili di ambiente di un sistema Unix o simile. Viene proposto un esempio di dichiarazione delle macro-variabili indispensabili, ma l'associazione al numero varia molto da un sistema all'altro:
|
La macro variabile successiva è un'estensione usata nei sistemi POSIX:
|
La funzione setlocale() con cui si cambia la configurazione locale ha il prototipo seguente:
|
Si prevedono due situazioni diverse di utilizzo della funzione. Per cominciare può essere usata per interrogare la configurazione attuale, come nell'esempio seguente:
|
Fornendo un puntatore nullo, al posto della stringa che deve indicare la configurazione locale, si ottiene un puntatore alla stringa che descrive quella attuale. In questo caso, se si utilizza la categoria LC_ALL, si ottiene una stringa che descrive tutte le altre categorie, ammesso che ci siano delle differenze. Se invece la funzione non è in grado di dare questa informazione, si ottiene semplicemente un puntatore nullo.
Naturalmente, la funzione serve anche per cambiare la configurazione locale, specificando in tal caso la stringa che la descrive. Per esempio, nel modo seguente si interviene nella categoria LC_NUMERIC:
|
Anche in questo caso si ottiene un puntatore che descrive la categoria scelta, ma se l'operazione fallisce, si ottiene invece il puntatore nullo.
Normalmente è più probabile che, nell'impostazione della configurazione locale si voglia indicare una modalità unica per tutte le categorie; pertanto, in tal caso va usato LC_ALL:
|
Viene mostrato un esempio di programma completo, in cui si imposta prima la configurazione locale complessiva, poi se ne cambia una e quindi si interroga la situazione:
|
Ecco cosa si può ottenere:
LC_COLLATE: "it_IT.UTF-8" LC_CTYPE: "it_IT.UTF-8" LC_MONETARY: "en_US.UTF-8" LC_NUMERIC: "it_IT.UTF-8" LC_TIME: "it_IT.UTF-8" LC_ALL: "LC_CTYPE=it_IT.UTF-8;LC_NUMERIC=it_IT.UTF-8;\ |
Per fare sì che il programma erediti la configurazione locale dal contesto in cui si trova a funzionare (quindi dalla configurazione locale del sistema operativo), si può indicare la stringa nulla al posto della definizione:
|
Pertanto, questo è il modo appropriato per iniziare la configurazione all'interno di un programma.
Il modo in cui si rappresenta testualmente un valore numerico, con o senza indicazione della valuta (la moneta), dipende dalla configurazione locale. La funzione localeconv() restituisce il puntatore a una struttura che contiene i dettagli riguardo alle modalità di rappresentazione dei valori numerici, secondo la configurazione locale. L'utilizzo consentito di questa struttura si limita all'interrogazione dei valori, perché la modifica dipende dalla gestione della configurazione locale.
|
A titolo di esempio vengono descritti solo alcuni membri della struttura; per gli altri si deve consultare la documentazione dello standard:
|
I membri che rappresentano delle stringhe (puntatori a carattere), quando si riferiscono a dati facoltativi, possono essere vuoti (nel senso di stringhe nulle). I membri di tipo char vengono usati in modo numerico.
|
Come si vede dal prototipo, la funzione localeconv() serve esclusivamente per ottenere il puntatore alla struttura lconv, da usare per la sua consultazione. Viene mostrato un esempio molto semplice per il suo utilizzo:
|
Il risultato che si ottiene dovrebbe essere molto simile a quello seguente:
decimal_point: "," thousand_sep: "" grouping: "" mon_decimal_point: "," mon_thousands_sep: "." mon_grouping: "" positive_sign: "" negative_sign: "-" currency_symbol: "€" frac_digits: 2 p_cs_precedes: 1 n_cs_precedes: 1 p_sep_by_space: 1 n_sep_by_space: 1 p_sign_posn: 1 n_sign_posn: 1 int_curr_symbol: "EUR " int_frac_digit: 2 int_p_cs_precedes: 1 int_n_cs_precedes: 1 int_p_sep_by_space: 1 int_n_sep_by_space: 1 int_p_sign_posn: 1 int_n_sign_posn: 4 |
Si osservi che, nell'esempio, la stringa a cui si accede tramite il membro currency_symbol è una sequenza «multibyte», nel senso che utilizza più byte per rappresentare un solo carattere.
Il file ctype.h
della libreria standard definisce alcune funzioni per la classificazione e la trasformazione dei caratteri (intesi come char). Gli esempi proposti qui riguardano esclusivamente l'insieme di caratteri corrispondente alla codifica ASCII e, di conseguenza, la configurazione locale C. Tuttavia va ricordato che il linguaggio C non impone che l'insieme di caratteri minimo sia descritto attraverso la codifica ASCII, mentre così è invece nello standard POSIX. (si veda eventualmente la realizzazione del file ctype.h
nei sorgenti di os32, listato 95.1.5).
Le funzioni di questo file hanno in comune il parametro, costituito da un valore intero di tipo int, usato per rappresentare il carattere.(1) Le funzioni del tipo is...() restituiscono un valore intero diverso da zero (corrispondente a Vero) se la condizione riferita al carattere fornito si verifica. Le funzioni to...() restituiscono un valore intero, corrispondente al carattere fornito e trasformato nel modo richiesto, se ciò è possibile.
|
|
Il gruppo di funzioni is...() restituisce un valore intero diverso da zero (corrispondente a Vero) se la condizione riferita al carattere fornito si verifica. Vengono proposte le varie soluzioni, affiancando la tabella ASCII con i caratteri validi evidenziati.
In alternativa a delle funzioni vere e proprie, si possono realizzare semplicemente delle macroistruzioni per verificare le condizioni riferite al carattere. Il listato seguente è conforme a quanto già visto nella sezione precedente:
|
Le due funzioni tolower() e toupper() si occupano di convertire un carattere, rispettivamente, in minuscolo o in maiuscolo.
Lo standard POSIX aggiunge anche la funzione toascii() che si limita ad azzerare i bit più significativi, dopo il settimo.
|
Anche le funzioni toupper(), tolower() e toascii() possono essere rappresentate agevolmente in forma di macroistruzioni. Il listato seguente è conforme a quanto già visto nella sezione precedente:
|
Ma nel caso dello standard POSIX, in questo caso vanno ancora aggiunge due macroistruzioni, a cui non fanno capo funzioni con lo stesso nome:
|
Viene proposto un programma molto semplice che utilizza tutte le funzioni dichiarate nel file ctype.h
, ma solo secondo lo standard C:
|
Una volta compilato il programma, avviandolo si deve ottenere un testo come quello che si vede nell'estratto seguente:
... 44 print graph upper alnum xdigit alpha 45 print graph upper alnum xdigit alpha 46 print graph upper alnum xdigit alpha 47 print graph upper alnum alpha 48 print graph upper alnum alpha ... ASCII: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJ\ |
Il file stdarg.h
della libreria standard definisce principalmente delle macroistruzioni per gestire gli argomenti variabili passati a una funzione, assieme a un tipo di variabile, va_list, specifico per gestire il puntatore a tali parametri non dichiarati (si veda eventualmente la realizzazione del file stdarg.h
nei sorgenti di os32, listato 95.1.10).
|
Il listato successivo è tutto ciò che serve per realizzare la libreria:
|
Delle macroistruzioni mostrate nell'esempio, la più difficile da interpretare potrebbe essere va_arg, la quale deve restituire il valore dell'area di memoria puntata inizialmente, ma garantendo di lasciare il puntatore pronto per l'area successiva. In pratica, prima viene incrementato il puntatore per l'area successiva, quindi viene dereferenziato ricalcolando lo spazio necessario a raggiungere la posizione precedente. In altri termini è come scrivere:
|
Viene riproposto un programma molto semplice, già apparso in altri capitoli, per dimostrare l'utilizzo delle macroistruzioni dichiarate nel file stdarg.h
.
|
Avviando il programma di esempio si deve visualizzare il messaggio seguente:
w = 10; x = 12.340000; y = 13; z = 14 |
Va ricordato che gli argomenti delle chiamate alle funzioni vengono adattati in modo tale da facilitare l'uso della pila dei dati. Pertanto, i valori che prevedono una rappresentazione in memoria troppo piccola, subiscono quella che è nota come «promozione».
La funzione che ha degli argomenti variabili, dovrebbe gestire solo valori che non possono subire una trasformazione di questo tipo, altrimenti, quando poi utilizza la macroistruzione va_arg deve indicare un tipo adeguato alla promozione che si prevede sia applicata ai valori degli argomenti.
A questo proposito si può notare che nell'esempio di utilizzo che appare nella sezione 69.8.2, non si fa mai uso di tipi di dati di rango inferiore a int.
Il file stdlib.h
della libreria standard definisce alcuni tipi di dati, varie funzioni di utilità generale e alcune macro-variabili. Viene proposto un esempio di questo file, e di alcune delle funzioni a cui si riferisce, indicando per le altre solo i prototipi (si veda eventualmente la realizzazione del file stdlib.h
e di alcune delle sue funzioni, nei sorgenti di os32, sezione 95.19).
Lo standard POSIX estende significativamente il contenuto del file stdlib.h
, ma qui non si fa riferimento ad alcuna di tali estensioni.
I tipi di dati che il file stdlib.h
definisce sono size_t e wchar_t, già descritti nel file stddef.h
(sezione 69.13), oltre a div_t, ldiv_t e lldiv_t. I tipi ...div_t sono delle strutture il cui scopo è quello di contenere il risultato di una divisione, espresso come quoziente e resto. Questi tipi di dati si usano per contenere il valore restituito dalle funzioni div(), ldiv() e lldiv(). La distinzione tra i tre tipi deriva dalla capienza dei membri della struttura. Ecco come potrebbero essere dichiarati:
|
Il file stdlib.h
dichiara nuovamente la macro-variabile NULL, come già avviene nel file stddef.h
(sezione 69.13); inoltre definisce quelle seguenti:
|
Merita un po' di attenzione la macro-variabile MB_CUR_MAX. La sequenza multibyte è una sequenza di byte che, in base alla configurazione locale, deve essere interpretata come un carattere singolo. Per esempio, questo meccanismo si utilizza nella codifica UTF-8 e in altre; ma proprio perché esistono più metodi alternativi, per quanto superati possano essere rispetto a UTF-8, la configurazione locale stabilisce le regole particolari per interpretare tali sequenze e i limiti rispetto a queste. Pertanto, la macro-variabile MB_CUR_MAX dovrebbe espandersi in una funzione che restituisce il valore desiderato, in relazione alla configurazione locale che si trova a essere attiva in un certo momento. Per semplicità, nell'esempio che viene proposto si associa il valore di questa macro-variabile a quello massimo accettabile in assoluto.
|
Nell'esempio proposto viene usata la macro-variabile MB_LEN_MAX, pertanto, in questo modo si rende necessaria l'inclusione del file limits.h
che deve contenere la sua dichiarazione.
Un gruppo di funzioni del file stdlib.h
permette di convertire una stringa in un valore numerico. In particolare, le funzioni con nomi del tipo ato...() (ASCII to ...) non eseguono controlli particolari e non modificano la variabile errno (sezione 69.5); invece, le funzioni con nomi strto...() (string to ...) sono più sofisticate.
Le funzioni ato...() interpretano una stringa e convertono il suo contenuto in un numero intero o in un numero a virgola mobile. Le funzioni sono atoi(), atol(), atoll() e atof(), che convertono rispettivamente in un tipo int, long int, long long int e double. Ecco i prototipi:
|
Viene proposta una soluzione per queste funzioni di conversione:
|
Logicamente, le funzioni atol() e atoll() sono praticamente uguali, con la differenza che la variabile automatica n deve essere dello stesso tipo restituito dalla funzione; pertanto si passa alla soluzione proposta per la funzione atof():
|
Le funzioni strto...() sono più complesse rispetto a quelle ato...(). Per dare una descrizione sommaria, si può osservare che, oltre alla stringa da scandire ricevono un puntatore di puntatore all'ultimo elemento utile di tale stringa;(2) se poi questo è nullo, la scansione avviene normalmente nella stringa, entro il limite del carattere nullo di terminazione. Se fallisce il riconoscimento del valore da tradurre, il puntatore all'inizio della stringa viene copiato nell'area di memoria a cui punta il puntatore di puntatore, a meno che questo, inizialmente, sia già nullo (potrebbe essere nullo il puntatore principale o il contenuto dell'area a cui punta).
In caso di errore nell'interpretazione del valore, queste funzioni utilizzano la variabile errno per annotare il tipo di problema riscontrato.
I valori che possono essere convertiti sono esprimibili in notazione decimale o esadecimale; inoltre, le funzioni che convertono in valori a virgola mobile, accettano una notazione esponenziale e delle parole chiave per rappresentare l'infinito e NaN (Not a number). Nel caso particolare delle funzioni che convertono in un numero intero, esiste un terzo parametro per specificare la base di numerazione attesa.
Vengono presentati solo i prototipi di queste funzioni:
|
|
Per una descrizione completa si vedano le pagine di manuale strtof(3), strtod(3), strtold(3), strtol(3), strtoll(3), strtoul(3) e strtoull(3), oltre alla documentazione standard citata alla fine del capitolo.
L'esempio seguente mostra l'uso delle funzioni ato...():
|
Il risultato che ci si attende di visualizzare è questo:
" -987654.3210" = -987654, -987654, -987654, -987654.321000 |
La libreria standard deve disporre, nel file stdlib.h
, di due funzioni per la generazione di numeri pseudo-casuali. Si tratta precisamente di rand() che restituisce un numero intero casuale (di tipo int, ma sono ammessi solo valori positivi) e di srand() che serve a cambiare il «seme» di generazione di tali numeri. Lo standard prescrive anche che per uno stesso seme, la sequenza di numeri pseudo-casuali sia la stessa e che il seme predefinito iniziale sia pari a uno.
Nella descrizione dello standard si fa riferimento al fatto che il valore che può essere generato deve andare da zero a RAND_MAX, escludendo quindi valori negativi. Considerando che la funzione rand() restituisce un valore di tipo int e che questo non può essere negativo, significa che RAND_MAX deve essere inferiore o uguale al massimo numero positivo rappresentabile con il tipo int.
|
Viene proposta una soluzione molto semplice e anche molto scadente sul piano della sequenza casuale generata. Tuttavia garantisce che il valore ottenuto vada effettivamente da zero a RAND_MAX incluso:
|
L'esempio seguente consente di verificare sommariamente il lavoro delle funzioni per la generazione di numeri casuali. Per la precisione, si vogliono ottenere valori che vanno da 0 a 99 inclusi:
|
Si dovrebbe ottenere un risultato simile a quello seguente:
86 67 22 15 22 47 94 91 10 99 6 31 22 51 98 11 14 11 22 3 22 11 82 7 54 11 62 75 10 63 50 75 98 91 90 79 74 87 18 15 34 7 54 71 42 47 10 23 82 67 50 19 14 31 34 27 58 71 38 43 14 87 38 71 82 95 50 99 2 7 30 7 98 87 2 19 46 71 78 83 34 43 58 99 70 79 30 75 58 99 46 31 78 91 34 79 98 75 14 99 66 63 82 99 2 51 46 11 74 39 90 31 54 63 78 39 14 31 50 55 26 87 2 51 70 15 98 67 54 27 34 55 14 7 74 71 42 7 30 87 70 67 34 15 26 47 90 91 30 19 66 27 86 15 26 15 6 99 46 7 14 51 22 43 54 95 58 7 18 15 50 83 26 71 2 27 38 71 30 7 94 19 6 47 62 19 90 39 54 39 42 15 86 91 82 87 38 27 34 87 74 87 50 87 82 67 78 63 22 3 26 87 86 11 94 71 26 35 10 83 78 71 46 63 82 47 74 63 70 11 54 95 62 31 74 87 30 71 54 15 18 19 14 79 86 23 58 7 98 47 10 63 46 39 26 19 10 67 54 99 6 47 70 11 26 7 66 23 10 51 66 31 58 91 74 87 22 35 74 11 6 39 58 39 26 35 94 55 82 47 78 67 98 91 30 67 18 75 74 83 18 23 54 51 26 91 38 11 22 23 66 91 18 99 74 27 82 99 62 35 58 35 54 95 2 95 86 95 82 47 90 51 90 51 54 23 62 91 90 99 18 19 62 51 10 59 26 39 82 71 94 83 98 27 38 7 22 15 90 79 18 31 58 71 22 3 58 39 98 63 62 55 38 51 86 51 2 7 86 87 94 43 54 67 62 23 42 31 10 3 42 47 74 87 26 27 54 43 54 95 18 31 34 27 58 47 66 47 70 75 22 35 82 7 54 7 62 19 18 91 22 63 94 83 98 27 86 59 78 91 78 35 62 67 90 67 62 35 78 95 86 99 54 47 14 3 62 19 58 63 74 11 62 99 46 71 86 7 34 3 34 59 18 3 62 43 62 27 26 95 46 3 22 15 54 35 6 75 86 27 58 51 70 71 82 15 50 39 38 91 14 99 66 67 66 91 54 31 34 3 30 55 98 27 50 7 38 11 26 11 42 47 26 31 10 91 90 15 54 19 50 87 70 35 14 79 10 47 74 55 34 51 54 95 58 23 46 59 78 91 38 3 94 51 70 3 54 51 86 51 14 95 70 55 50 99 70 63 86 15 6 35 98 67 74 91 54 99 30 7 94 35 10 43 54 55 50 11 58 75 26 27 78 99 98 55 94 79 26 83 34 87 38 47 6 55 74 23 10 11 26 87 6 59 10 71 86 31 78 55 30 39 66 47 46 11 30 47 2 11 74 55 42 59 2 59 42 63 78 71 2 19 6 83 30 23 2 31 54 47 62 31 26 67 6 67 70 63 14 91 |
Il documento che descrive lo standard descrive una versione della funzione rand() che corrisponde al listato successivo. I valori usati nei calcoli sono tali da essere adatti a un contesto in cui i limiti degli interi sono quelli minimi previsti.
|
Un gruppo di funzioni dichiarate nel file strlib.h
consente di utilizzare dinamicamente la memoria. Si tratta di malloc(), calloc(), realloc() e free(). Le prime tre funzioni restituiscono un puntatore di tipo void * all'area di memoria allocata, oppure il puntatore nullo nel caso l'operazione di allocazione fallisca; la funzione free() libera un'area di memoria allocata, indicando come argomento il puntatore che inizialmente la rappresentava.
|
Rispetto ai prototipi mostrati, la funzione malloc() richiede l'allocazione di una quantità di byte espressa dal parametro size; calloc() richiede una quantità di nmemb elementi da size byte (pertanto serve solo a facilitare l'allocazione di uno spazio necessario a un array); realloc() richiede la riallocazione della memoria già allocata precedentemente a partire dall'indirizzo ptr per avere size byte, con l'intento di non perdere le informazioni precedenti (a meno che si tratti di una riduzione della dimensione); infine, free() si limita a deallocare la memoria a cui punta ptr.
Nel linguaggio C, la memoria deve essere allocata e liberata espressamente, in quanto non esiste alcun sistema automatico al riguardo. |
La gestione della memoria dipende strettamente dal sistema operativo, pertanto la realizzazione delle funzioni non può essere generalizzata. Per i dettagli che riguardano il comportamento di queste funzioni nel proprio sistema operativo vanno consultate le pagine di manuale malloc(3), calloc(3), realloc(3) e free(3).
Alcune funzioni si occupano di interrompere il funzionamento del programma al di fuori della conclusione naturale della funzione main(). In generale si possono distinguere i casi in cui la conclusione del programma viene gestita in modo gentile, oppure viene forzata brutalmente.
Per una conclusione corretta di un programma, è possibile predisporre un elenco di funzioni da eseguire automaticamente nel momento della conclusione. Ciò avviene attraverso la funzione atexit() che accumula un elenco di puntatori a funzione; successivamente, attraverso la chiamata alla funzione exit() si ottiene l'esecuzione delle funzioni dell'elenco, senza argomenti, secondo l'ordine di inserimento. Quindi, la funzione exit() conclude con la chiusura dei file e con la restituzione del valore passatole come argomento.
Una conclusione brutale si ottiene con la funzione _Exit(), che si limita a terminare il programma, ma senza fare altro, soprattutto senza garantire che i file aperti siano chiusi correttamente.
Per ottenere una conclusione brutale del funzionamento di un programma si può usare anche la funzione abort() che però è legata alla gestione dei segnali (sezione 69.15) e qui non viene spiegato il suo utilizzo.
|
La funzione atexit() riceve come unico argomento il puntatore a una funzione, la quale non restituisce alcun valore (di tipo void) e non si attende alcun argomento (ancora il tipo void). La funzione atexit() restituisce un valore numerico da intendere come Vero o Falso, per comunicare il successo o l'insuccesso dell'operazione, dato che la quantità di puntatori a funzione che possono essere accumulati può avere un limite.
Per una descrizione completa dell'uso di queste funzioni si vedano le pagine di manuale abort(3), atexit(3), exit(3) e _Exit(3).
Nel file stdlib.h
sono dichiarate due funzioni per interagire con il sistema operativo, getenv() e system(), dove la prima consente di interrogare le variabili di ambiente (nel senso inteso nei sistemi Unix ed equivalenti) e la seconda consente di eseguire dei comandi attraverso la shell.
Nel documento che descrive lo standard del linguaggio C, il concetto viene generalizzato, ma in pratica, il contesto da cui derivano queste funzioni è quello dei sistemi Unix.
|
La funzione getenv() si aspetta di ricevere come argomento il nome di una variabile di ambiente (o di qualcosa di comparabile, nel contesto di un altro tipo di sistema operativo), restituendo il puntatore al contenuto di tale variabile. La funzione system() può essere usata indicando un puntatore nullo e in tal caso restituisce un valore diverso da zero se il sistema operativo è in grado di recepire dei comandi testuali. Se invece viene passata una stringa, la funzione tenta di farla eseguire come comando del sistema operativo: in un sistema Unix o equivalente si tratta di un comando che deve essere eseguito da /bin/sh
. L'esito della funzione system() dipende da quello del comando impartito e generalmente si ottiene lo stesso valore restituito dal comando eseguito.
Si vedano le pagine di manuale getenv(3) e system(3).
Il file stdlib.h
prevede la dichiarazione di due funzioni per il riordino degli array e per la ricerca all'interno di array ordinati. Si tratta precisamente delle funzioni qsort() e bsearch(), dove i nomi richiamano evidentemente gli algoritmi tradizionali noti come quick sort e binary search.
Le funzioni della libreria standard generalizzano il problema dell'ordinamento e della ricerca utilizzando puntatori di tipo void * e scandendo la memoria a blocchi di una dimensione determinata. Ma dal momento che l'area di memoria da scandire non ha la personalità di un array di un qualche tipo, occorre fornire a entrambe queste funzioni il puntatore a una funzione diversa, in grado di confrontare due valori nel contesto di proprio interesse.
|
Prima di descrivere il significato dei parametri delle due funzioni, conviene vedere un esempio in cui queste si utilizzano. Per la precisione viene scandito un piccolo array di elementi di tipo int: prima viene ordinato, poi si cerca un elemento al suo interno.
|
Nell'esempio viene dichiarata la funzione confronta() che riceve due argomenti e restituisce un valore che può essere: minore, pari o maggiore di zero, se il primo argomento, rispetto al secondo, è minore, pari o maggiore. Questo è il modo in cui deve comportarsi la funzione da passare come argomento a qsort() e a bsearch(), tenendo conto che è da tali funzioni che riceve gli argomenti.
La funzione qsort() vuole ricevere il puntatore alla prima posizione in memoria da riordinare (il parametro base), la quantità degli elementi da riordinare (nmemb, ovvero Number of memory blocks), la dimensione di tali elementi (size) e la funzione da usare per la loro comparazione.
La funzione bsearch() vuole ricevere il puntatore alla chiave di ricerca (il parametro key), il puntatore alla prima posizione in memoria da scandire (base), la quantità degli elementi da scandire (nmemb), la dimensione di tali elementi (size) e la funzione da usare per la loro comparazione, tenendo conto che questa riceve la chiave di ordinamento come primo argomento.
L'esempio mostrato esegue un ordinamento crescente e il testo visualizzato che si ottiene deve essere simile a quello seguente:
1 2 3 5 &a[0] = 3218927260; "5" si trova in 3218927272. |
È sufficiente invertire il risultato della funzione di comparazione per ottenere un ordinamento decrescente e per scandire un array ordinato in modo decrescente:
|
In tal caso, il testo che viene emesso deve essere simile a quello seguente:
5 3 2 1 &a[0] = 3218593340; "5" si trova in 3218593340. |
Un gruppo di funzioni il cui nome termina per ...abs() si occupa di calcolare il valore assoluto di un numero intero. Le funzioni sono precisamente: abs() per gli interi di tipo int, labs() per gli interi di tipo long int e llabs() per gli interi di tipo long long int.
|
Evidentemente, la realizzazione di queste funzioni è estremamente banale. Viene presentato solo il caso di abs():
|
Un gruppo di funzioni il cui nome termina per ...div() si occupa di dividere due interi, calcolando il quoziente e il resto. Le funzioni sono precisamente: div() per gli interi di tipo int, ldiv() per gli interi di tipo long int e lldiv() per gli interi di tipo long long int. Il risultato viene restituito in una variabile strutturata che contiene sia il quoziente, sia il resto: rispettivamente si tratta dei tipi div_t, ldiv_t e lldiv_t.
|
I tre tipi creati appositamente per contenere il risultato di queste funzioni contengono i membri quot e rem che rappresentano, rispettivamente, il quoziente e il resto. Anche la realizzazione di queste funzioni è molto semplice banale. Viene presentato solo il caso di div():
|
Il linguaggio C distingue tra una gestione dei caratteri basata sul byte, tale da consentire la gestione di un insieme minimo, come quello della codifica ASCII, e una gestione a byte multipli, o multibyte. Per esempio, la codifica UTF-8 è ciò che si intende per «multibyte», ma esistono anche altre codifiche che sfruttano questo meccanismo.
Quando il contesto richiede l'interpretazione dei byte secondo una codifica multibyte, è necessario stabilire un punto di riferimento per iniziare l'interpretazione e occorre poterne conservare lo stato quando la lettura di un carattere viene interrotta e ripresa a metà. Nella documentazione dello standard, nell'ambito delle sequenze multibyte, lo stato viene definito shift state.
Per gestire internamente la codifica universale, il C utilizza un tipo specifico, wchar_t, corrispondente a un intero di rango sufficiente a rappresentare tutti i caratteri che si intendono gestire. Di conseguenza, le stringhe letterali, precedute dalla lettera L (per esempio L"àèìòùé"), sono array di elementi wchar_t.
Le funzioni che riguardano la gestione di caratteri estesi e sequenze multibyte del file stdlib.h
, servono principalmente per convertire sequenze multibyte nel tipo wchar_t e viceversa. Tuttavia, occorre tenere presente che la configurazione locale deve essere tale da prevedere l'uso di caratteri da rappresentare attraverso sequenze multibyte, altrimenti le conversioni diventano prive di utilità.
|
La funzione mblen() si usa normalmente per contare quanti byte sono presenti nella stringa s fornita come primo argomento, per comporre il primo carattere (multibyte) della stringa stessa, limitando la scansione a un massimo di n byte (il secondo argomento richiesto). Se al posto di indicare una stringa si fornisce il puntatore nullo, si ottiene un valore che può essere uno o zero, a seconda che sia prevista o meno una codifica multibyte con una gestione dello stato (shift state).
|
L'esempio mostrato dovrebbe chiarire alcune cose. La funzione richiede un argomento di tipo stringa di caratteri, perché un argomento di tipo char singolo, non consentirebbe di annotare una sequenza multibyte. Le sequenze multibyte sono stringhe normali, trattate come tali, salvo quando è necessario interpretare il loro contenuto; a questo proposito, si vede che la funzione printf() riceve una stringa multibyte e si limita a trattarla come una stringa normale.
Se la codifica in cui è scritto il sorgente è la stessa usata dal programma durante il suo funzionamento, si può ottenere il testo seguente:
Gestione dello stato: 0 Il carattere € richiede 3 byte. |
Nell'uso normale della funzione mblen(), se la stringa che si fornisce contiene una sequenza multibyte errata o incompleta, il valore restituito è -1.
Le due funzioni mbtowc() e wctomb() si compensano a vicenda, fornendo il mezzo elementare di conversione dei caratteri da una sequenza multibyte a un numero intero di tipo wchar_t e viceversa. Quando a queste funzioni, al posto del puntatore alla stringa multibyte, si fornisce il puntatore nullo, si ottiene un funzionamento analogo a quello di mblen(), con un valore pari a uno se la configurazione locale prevede l'uso di sequenze multibyte e una gestione dello stato, oppure zero se questo problema non sussiste. Inoltre, per entrambe le funzioni, se la sequenza multibyte è errata o incompleta, si ottiene la restituzione del valore -1. Infine, se la conversione ha successo, si ottiene la quantità dei byte che compongono la sequenza multibyte (di origine o di destinazione, a seconda della funzione usata).
Viene mostrato un esempio molto semplice che dimostra l'uso delle due funzioni. In particolare viene convertita la sequenza multibyte che rappresenta la lettera «ä» in un numero wchar_t, quindi il numero viene incrementato e riconvertito in una nuova sequenza multibyte, per ottenere il carattere «å».
|
Si dovrebbe ottenere un testo come quello seguente:
Gestione dello stato: 0 Gestione dello stato: 0 Il carattere "ä" si rappresenta con 2 byte in una \ |
Per gli approfondimenti eventuali, si vedano le pagine di manuale mbtowc(3) e wctomb(3).
Le funzioni mbstowcs() e wcstombs servono rispettivamente per convertire una stringa multibyte in un stringa estesa (un array di elementi wchar_t) e per fare l'opposto. Entrambe le funzioni richiedono tre argomenti: l'array di destinazione, l'array di origine e la quantità di elementi da utilizzare nell'array di destinazione. Entrambe le funzioni restituiscono un numero che esprime la quantità di elementi di destinazione convertiti, escluso ciò che costituisce il carattere nullo di terminazione. Entrambe restituiscono un valore pari a (size_t) (-1) se la conversione produce un errore.(3)
Per convertire correttamente una stringa (multibyte o estesa), occorre che il numero di elementi di destinazione previsto includa anche il carattere nullo di terminazione. L'esempio seguente dovrebbe aiutare a comprendere il problema:
|
Nell'esempio, la funzione mbstowcs() viene usata due volte, per convertire una stringa multibyte, composta da tre caratteri, se non si conta quello di terminazione. Nel primo caso, viene specificato che si vogliono convertire esattamente tre caratteri, ma questo significa che nell'array di destinazione rimane il contenuto originale a partire dal quarto elemento. In modo analogo, la funzione wcstombs() viene usata due volte per convertire una stringa estesa in una stringa multibyte. La stringa estesa si compone di tre caratteri che nella conversione vanno a occupare esattamente sei byte, con l'aggiunta eventuale del carattere nullo di terminazione (che sarebbe il settimo). Si può vedere che quando si chiede una conversione di sei elementi, la stringa ricevente mantiene il contenuto precedente nella parte restante. Ecco cosa si dovrebbe vedere eseguendo il programma:
mbstowcs: 3: 228 229 226 4 5 mbstowcs: 3: 228 229 226 0 5 wcstombs: 6: "äåâ********" wcstombs: 6: "äåâ" |
Se al posto della destinazione (il primo argomento) viene posto il puntatore nullo, si ottiene la simulazione dell'operazione, senza memorizzare alcunché e senza tenere conto della quantità massima di elementi che si annota come ultimo argomento. Ciò ha lo scopo di contare quanti elementi servirebbero per produrre una conversione completa. L'esempio seguente modifica quello già visto, sfruttando questa funzionalità:
|
Ecco cosa si dovrebbe vedere eseguendo il programma:
mbstowcs: 3: 228 229 226 0 5 wcstombs: 6: "äåâ" |
Per gli approfondimenti eventuali, si vedano le pagine di manuale mbstowcs(3) e wcstombs(3).
Il file inttypes.h
della libreria standard serve principalmente a completare le funzionalità di stdint.h
, per ciò che riguarda la gestione dei valori numerici interi, il cui rango è controllabile. Infatti, il problema principale nell'uso di interi definiti in modo alternativo allo standard del linguaggio C, privo di librerie, sta nell'uso appropriato degli specificatori di conversione nelle funzioni come printf() e scanf(). È proprio per risolvere questo problema che nel file inttypes.h
vanno definite, soprattutto, delle macro-variabili da usare in sostituzione degli specificatori di conversione basati sui tipi elementari (si veda eventualmente la realizzazione del file inttypes.h
, ma senza le funzioni che lo riguardano, nei sorgenti di os32, listato 95.8).(4)
Gli esempi proposti per descrivere la libreria che fa capo al file inttypes.h
si riferiscono a quanto già definito nella sezione 69.4 a proposito del file stdint.h
.
Inizialmente, il file inttypes.h
deve includere stdint.h
, inoltre dichiara il tipo wchar_t, già descritto nel file stddef.h
:
|
Nel file inttypes.h
viene definito il tipo imaxdiv_t che va ad affiancarsi ai tipi div_t, ldiv_t e ldiv_t, definiti nel file stdlib.h
. In pratica si tratta di una struttura il cui scopo è quello di contenere il risultato di una divisione, espresso come quoziente e resto, quando il tipo intero usato è quello massimo:
|
Il tipo strutturato imaxdiv_t serve alle funzioni imaxdiv() e uimaxdiv(), le quali sono sostanzialmente equivalenti alle altre funzioni ...div() del file stdlib.h
:
|
|
Come accennato all'inizio del capitolo, per poter usare le funzioni ...printf() e ...scanf(), occorrono degli specificatori di conversione, ma non ne esistono per i tipi interi a rango controllato, pertanto, per questi, servono delle macro-variabili coerenti con il tipo relativo.
Le macro che iniziano per PRI... si usano come parte terminale di specificatori di conversione per la composizione dell'output (...printf()), mentre le macro che iniziano per SCN... sono adatte per l'interpretazione dell'input (...scanf()).
Le macro PRIxn e SCNxn terminano gli specificatori di conversione %...x, per i tipi interi [u]intn_t; le macro PRIxLEASTn e SCNxLEASTn riguardano i tipi [u]int_leastn_t; le macro PRIxFASTn e SCNxFASTn riguardano i tipi [u]int_fastn_t; le macro PRIxMAXn e SCNxMAXn riguardano i tipi [u]intmax_t; le macro PRIxPTRn e SCNxPTRn riguardano i tipi [u]intptr_t.
L'esempio seguente dovrebbe dimostrare il significato di queste macro-variabili, attraverso l'uso di printf():
|
Il listato seguente mostra come possono essere dichiarate queste macro-variabili:
|
Nel file stdlib.h
si trovano dichiarate alcune funzioni per il calcolo del valore assoluto: ...abs(). Nel file inttypes.h
si aggiunge la funzione imaxabs(), da usare per i valori interi massimi:
|
|
Per convertire una stringa contenente un valore numerico intero, quando si vuole fare riferimento all'intero di dimensione massima, si possono usare le funzioni strtoimax(), strtouimax(), wcstoimax() e wcstouimax(), dichiarate nel file inttypes.h
. Come il nome suggerisce, le prime due funzioni sono destinate alla conversione di stringhe «normali», mentre le altre sono specifiche per le stringhe estese.
Evidentemente si tratta di funzioni che si abbinano alle altre strto...() del file stdlib.h
e alle funzioni wcsto...() del file wchar.h
.
|
Come si vede, i parametri delle funzioni sono gli stessi; quello che cambia è il tipo di stringa, che nelle funzioni strto...() è normale, mentre nelle funzioni wcsto...() è di tipo esteso. Nel caso di funzioni ...touimax() si ottiene un valore intero senza segno, mentre con le funzioni ...toimax() si ottiene un valore intero con segno.
Il comportamento di queste funzioni è analogo a quello delle altre funzioni strto...() e wcsto...(), per ciò che riguarda l'interpretazione di valori interi, con la differenza che si fa riferimento al valore intero più grande. Il valore restituito è zero se non si può procedere alla conversione; se invece il valore è al di fuori dell'intervallo rappresentabile, a seconda dei casi si può avere il valore corrispondente a INTMAX_MAX, INTMAX_MIN o UINTMAX_MIN, con l'aggiornamento della variabile errno al valore rappresentato da ERANGE.
Il file iso646.h
della libreria standard definisce alcune macro-variabili da usare in sostituzione di simboli che potrebbero mancare nella propria tastiera, anche se ciò è comunque poco probabile.(5)
|
Il file stdbool.h
della libreria standard definisce alcune macro-variabili da usare per la gestione dei valori logici (Vero e Falso); in particolare consente di utilizzare il nome bool al posto di _Bool (si veda eventualmente la realizzazione di questo file nei sorgenti di os32, listato 95.1.11).(6).
|
Come si può vedere, la macro-variabile __bool_true_false_are_defined consente di sapere se le macro-variabili bool, true e false, sono definite.
Il file stddef.h
della libreria standard definisce alcuni tipi di dati e delle macro fondamentali (si veda eventualmente la realizzazione del file stddef.h
nei sorgenti di os32, listato 95.1.12).(7)
|
Di tutte le definizioni merita attenzione la macroistruzione offsetof che serve a misurare lo scostamento di un membro di una struttura, per la quale è il caso di scomporre i suoi componenti:
l'espressione ((tipo_struttura *)0) rappresenta un puntatore nullo trasformato, con un cast, in un puntatore nullo al tipo di struttura alla quale si sta facendo riferimento;
l'espressione ((tipo_struttura *)0)->nome_membro rappresenta il contenuto del membro indicato, preso a partire dall'indirizzo zero;
l'espressione &((tipo_struttura *)0)->nome_membro rappresenta l'indirizzo del membro indicato, preso a partire dall'indirizzo zero.
Pertanto, l'indirizzo del membro, relativo all'indirizzo zero, corrisponde anche al suo scostamento a partire dall'inizio della struttura. Così, tale valore viene convertito con un cast nel tipo size_t.
Il file string.h
della libreria standard definisce il tipo size_t, la macro-variabile NULL (come dal file stddef.h
, descritto nella sezione 69.13) e una serie di funzioni per il trattamento delle stringhe o comunque di sequenze di caratteri (si veda eventualmente la realizzazione del file string.h
e di alcune delle sue funzioni nei sorgenti di os32, sezione 95.20)).
Seguono i prototipi delle funzioni disponibili per la copia:
|
Lo standard POSIX aggiunge anche i prototipi seguenti:
|
La funzione memcpy() copia n caratteri a partire dall'indirizzo indicato da org, per riprodurli a partire dall'indirizzo dst, alla condizione che i due insiemi non risultino sovrapposti. La funzione restituisce l'indirizzo dst.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, dove la variabile y viene sovrascritta dal contenuto di x, ma questo attraverso la copia dei byte (si intende che gli interi siano da 32 bit).
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
prima: ffffffff dopo: 12345678 |
La funzione memccpy() appartiene allo standard POSIX e si distingue rispetto a memcpy() perché la copia termina al raggiungimento di un certo carattere (il parametro c), restituendo il puntatore alla posizione successiva nella destinazione.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, dove l'array y viene sovrascritto dal contenuto di x, fino a quando si raggiunge il carattere 7. Nell'esempio vengono anche visualizzati i valori dei puntatori di y e della posizione raggiunta all'interno di y alla fine della copia, sempre in forma di puntatore.
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
prima: "abcdefghij" 3218609914 dopo: "01234567ij" 3218609922 |
Come si può vedere dal risultato, l'array y inizia a partire dal puntatore 3 218 609 914 e, alla fine del trasferimento parziale dall'array x, che si ferma al carattere 7, la funzione restituisce il puntatore 3 218 609 922 che individua la posizione successiva al carattere copiato, corrispondente in pratica al carattere i.
La funzione memmove() opera in modo simile a memcpy(), con la differenza che le due aree di memoria coinvolte possono sovrapporsi. In pratica la copia avviene prima in un'area temporanea, quindi, dall'area temporanea viene ricopiata nella destinazione. La funzione restituisce l'indirizzo dst.
|
Per osservare il comportamento della funzione si può riutilizzare lo stesso programma usato per memcpy(), con la modifica del nome della funzione chiamata. Il risultato atteso è lo stesso:
|
La funzione strcpy() copia la stringa org nell'array a cui punta dst, includendo anche il carattere zero di conclusione delle stringhe, alla condizione che le due stringhe non si sovrappongano. La funzione restituisce dst.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, dove l'array y viene sovrascritto dal contenuto di x.
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
prima: ciao dopo: abcdefghijklmnopqrstuvwxyz |
La funzione strncpy() agisce in modo analogo a quello di strcpy, con la differenza che la copia riguarda al massimo i primi n caratteri, includendo in questo anche il carattere nullo di terminazione delle stringhe. Se però la stringa org è più breve (in quanto si incontra il carattere di terminazione prima di n caratteri), i caratteri rimasti vengono copiati con un valore a zero nella destinazione. La funzione restituisce dst.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, dove l'array y viene sovrascritto dal contenuto di x.
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
prima: ciaociaociaociaociaociaociaociao durante: abcdefghijaociaociaociaociaociao dopo: abcdefghijklmnopqrstuvwxyz |
La funzione strdup(), richiesta dallo standard POSIX, è simile a strcpy(), con la differenza che richiede solo l'indicazione della stringa da duplicare, mentre alloca autonomamente la memoria per produrne una copia. Pertanto, la funzione restituisce il puntatore alla stringa duplicata (e allocata) ed è poi possibile liberare la memoria attraverso la funzione free().
|
Nell'esempio proposto, si riutilizza la funzione strcpy() e la funzione malloc() per allocare la memoria necessaria. Viene anche usata la macro-variabile SIZE_MAX, per dare un limite massimo alla scansione, nel caso la stringa di origine non contenga il carattere nullo di terminazione. Per questa ragione, diventa necessario includere i file stdint.h
e stdlib.h
.
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, dove l'array o viene copiato altrove, associandogli il puntatore d: se l'operazione ha successo, viene visualizzata la stringa a cui punta d, altrimenti si ottiene un messaggio di errore. Dovendo usare la funzione free() per liberare la memoria, si include anche il file stdlib.h
.
|
Seguono i prototipi delle funzioni per il concatenamento:
|
La funzione strcat() copia la stringa org a partire dalla fine della stringa dst (sovrascrivendo il carattere nullo preesistente), alla condizione che le due stringhe non siano sovrapposte. La funzione restituisce dst.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, dove la stringa y viene estesa con il contenuto di x.
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
prima: ciao dopo: ciaoabcdefghijklmnopqrstuvwxyz |
La funzione strncat() si comporta in modo analogo a strcat(), con la differenza che copia al massimo n caratteri, ammesso che la stringa org ne contenga abbastanza. In ogni caso, la stringa dst viene completata con il carattere nullo di terminazione.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, dove la stringa y viene estesa con il contenuto di x, in due fasi.
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
prima: ciao durante: ciaoabcdefghij dopo: ciaoabcdefghijabcdefghijklmnopqrstuvwxyz |
Le funzioni di comparazione memcmp(), strcmp() e strncmp() confrontano due sequenze di caratteri, determinando se la prima sia maggiore, minore o uguale rispetto alla seconda, scandendo i caratteri progressivamente e arrestando l'analisi appena si incontra una differenza. Pertanto, il carattere che differisce è quello che determina l'ordine tra le due sequenze.
|
La funzione memcmp() confronta i primi n caratteri delle aree di memoria a cui puntano s1 e s2, restituendo: un valore pari a zero se le due sequenze si equivalgono; un valore maggiore di zero se la sequenza di s1 è maggiore di s2; un valore minore di zero se la sequenza di s1 è minore di s2.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, dove le variabili x e y sono interi (che si presume siano a 32 bit) rappresentati in memoria invertendo l'ordine dei byte (little endian), pertanto il confronto avviene in modo inverso all'apparenza dei simboli.
|
Avviando questo programma nelle condizioni descritte, si deve ottenere un risultato come quello seguente:
memcmp: 123456ff 1 eeeeeeee memcmp: 123456ff 0 123456ff memcmp: eeeeeeee -1 123456ff |
La funzione strcmp() confronta due stringhe restituendo: un valore pari a zero se sono uguali; un valore maggiore di zero se la stringa s1 è maggiore di s2; un valore minore di zero se la stringa s1 è minore di s2.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente:
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
strcmp: ciao -1 ciao amore strcmp: ciao 0 ciao strcmp: ciao amore 1 ciao |
La funzione strcoll() è analoga a strcmp(), con la differenza che la comparazione avviene sulla base della configurazione locale (la categoria LC_COLLATE). Nel caso della configurazione locale C la funzione si comporta esattamente come strcmp().
La funzione strncmp() si comporta in modo analogo a strcmp(), con la differenza che la comparazione si arresta al massimo dopo n caratteri.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente:
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
strncmp: 4 CIao 1 CIAO strncmp: 2 CIao 0 CIao strncmp: 4 CIAO -1 CIao |
La funzione strxfrm() trasforma la stringa org sovrascrivendo la stringa dst in modo relativo alla configurazione locale. In pratica, la stringa trasformata che si ottiene può essere comparata con un'altra stringa trasformata nello stesso modo attraverso la funzione strcmp() ottenendo lo stesso esito che si avrebbe confrontando le stringhe originali con la funzione strcoll().
Nella stringa di destinazione vengono messi non più di n caratteri, incluso il carattere nullo di terminazione. Se n è pari a zero, dst può essere un puntatore nullo. Le due stringhe non devono sovrapporsi.
La funzione strxfrm() restituisce la quantità di caratteri necessari a contenere la stringa org trasformata, senza però contare il carattere nullo di terminazione. Se n è zero e dst corrisponde al puntatore nullo, restituisce il valore che sarebbe necessario per trasformare la stringa org in tutta la sua lunghezza.
L'esempio seguente di tale funzione è valido solo per la configurazione locale C:
|
Seguono i prototipi delle funzioni utili per la ricerca all'interno di sequenze di byte, secondo lo standard C:
|
Lo standard POSIX aggiunge anche il prototipo seguente:
|
La funzione memchr() cerca un carattere a partire da una certa posizione in memoria, scandendo al massimo una quantità determinata di caratteri, restituendo il puntatore al carattere trovato. Se nell'ambito specificato non trova il carattere, restituisce il puntatore nullo.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente, in cui si scandisce il contenuto di una variabile di tipo int, intendendo che questa debba occupare uno spazio di 32 bit:
|
Avviando questo programma in un'architettura che inverte l'ordine dei byte (little endian) si deve ottenere un risultato simile a quello seguente:
contenuto della variabile: 13579bdf indirizzo iniziale della variabile: 0xbff7f3bc indirizzo di 0xdf all'interno della variabile: 0xbff7f3bc |
La funzione strchr() cerca un carattere all'interno di una stringa, restituendo il puntatore al carattere trovato, oppure il puntatore nullo se la ricerca fallisce. Nella scansione viene preso in considerazione anche il carattere nullo di terminazione della stringa.
|
Per verificare sommariamente il comportamento della funzione si può realizzare un programma molto semplice come quello seguente:
|
Avviando questo programma si deve ottenere un risultato simile a quello seguente:
La stringa "ciao amore mio", collocata a partire \ |
La funzione strrchr() cerca un carattere all'interno di una stringa, restituendo il puntatore all'ultimo carattere corrispondente trovato, oppure il puntatore nullo se la ricerca fallisce. Nella scansione viene preso in considerazione anche il carattere nullo di terminazione della stringa.
|
Per verificare sommariamente il comportamento della funzione si può modificare leggermente l'esempio già apparso a proposito della funzione strchr():
|
Avviando questo programma si deve ottenere un risultato simile a quello seguente:
La stringa "ciao amore mio", collocata a partire \ |
La funzione strspn() calcola la lunghezza massima iniziale della stringa s, composta esclusivamente da caratteri contenuti nella stringa accept, restituendo tale valore.
|
Per verificare sommariamente il comportamento della funzione si può utilizzare l'esempio seguente:
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
La parte iniziale di "ciao amore mio" che contiene i \ |
La funzione strcspn() si comporta in modo analogo a strspn(), con la differenza che l'insieme di caratteri contenuto nella stringa reject non deve costituire l'insieme iniziale della stringa s che si va a contare. Pertanto, il valore restituito è la quantità di caratteri iniziali della stringa s che non si trovano anche nell'insieme reject.
|
Per verificare sommariamente il comportamento della funzione si può utilizzare l'esempio seguente:
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
La parte iniziale di "ciao amore mio" che non contiene i \ |
La funzione strpbrk() scandisce la stringa s alla ricerca del primo carattere che risulti contenuto nella stringa accept, restituendo il puntatore al carattere trovato, oppure, in mancanza di alcuna corrispondenza, il puntatore nullo.
|
Per verificare sommariamente il comportamento della funzione si può utilizzare l'esempio seguente:
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
La stringa "ciao amore mio" che inizia all'indirizzo \ |
La funzione strstr() cerca la stringa substring nella stringa string restituendo il puntatore alla prima corrispondenza trovata (nella stringa string). Se la corrispondenza non c'è, la funzione restituisce il puntatore nullo.
|
Per verificare sommariamente il comportamento della funzione si può utilizzare l'esempio seguente:
|
Avviando questo programma si deve ottenere un risultato come quello seguente:
La stringa "ciao amore mio" che inizia all'indirizzo \ |
La funzione strtok() serve a suddividere una stringa in unità, definite token, specificando un elenco di caratteri da intendere come delimitatori, in una seconda stringa. La funzione va usata in fasi successive, fornendo solo inizialmente la stringa da suddividere che continua poi a essere utilizzata se al suo posto viene fornito il puntatore nullo. La funzione restituisce, di volta in volta, il puntatore alla sottostringa contenente l'unità individuata, oppure il puntatore nullo, se non può trovarla.
La funzione deve tenere memoria di un puntatore in un'area di memoria persistente (quello che nei commenti viene definito «puntatore statico») e deve isolare le unità modificando la stringa originale, inserendo il carattere nullo di terminazione alla fine delle unità individuate.
Quando la funzione viene chiamata indicando al posto della stringa da scandire il puntatore nullo, l'insieme dei delimitatori può essere diverso da quello usato nelle fasi precedenti.
|
Per comprendere lo scopo della funzione viene utilizzato lo stesso esempio che appare nel documento ISO/IEC 9899:TC2, al paragrafo 7.21.5.7, con qualche piccola modifica per poterlo rendere un programma autonomo:
|
Avviando il programma si ottiene quanto già descritto dai commenti inseriti nel codice:
strtok: "a" strtok: "??b" strtok: "c" strtok: "(null)" |
Ciò che avviene nell'esempio può essere schematizzato dalle figure successive. Inizialmente la stringa str ha in memoria l'aspetto seguente:
Dopo la prima chiamata della funzione strtok() la stringa risulta alterata e il puntatore ottenuto raggiunge la lettera a:
Dopo la seconda chiamata della funzione, in cui si usa il puntatore nullo per richiedere una scansione ulteriore della stringa originale, si ottiene un nuovo puntatore che, questa volta, inizia a partire dal quarto carattere, rispetto alla stringa originale, dal momento che il terzo è già stato sovrascritto da un carattere nullo:
La penultima chiamata della funzione strtok() raggiunge la lettera c che è anche alla fine della stringa originale:
L'ultimo tentativo di chiamata della funzione non può dare alcun esito, perché la stringa originale si è già conclusa.
Va tenuto in considerazione che la funzione strtok(), dovendo mantenere in memoria la posizione trovata dell'ultima scansione eseguita, da una chiamata a quella successiva, non è «rientrante», pertanto non si presta per i programmi che si suddividono in più thread. |
La funzione strtok_r(), richiesta dallo standard POSIX, salva il puntatore interno alla stringa che viene scandita, esternamente, in modo da poter essere usata in un contesto in cui più thread operano simultaneamente. In pratica si aggiunge un terzo parametro, costituito da un puntatore a puntatore a carattere.
Il puntatore a carattere (char *), il cui puntatore viene fornito come terzo argomento della funzione, deve essere dichiarato prima della chiamata della funzione. La prima volta che viene chiamata la funzione strtok_r() non conta quale valore abbia effettivamente tale variabile di tipo char *, perché è la funzione stessa che lo inizializza, ma nelle chiamate successive (quando al posto della stringa si dà alla funzione il valore NULL). Quando termina la scansione della stringa con le chiamate di strtok_r(), la variabile char * di cui si passa il puntatore, può essere utilizzata per altri scopi, o per altre scansioni.
La soluzione seguente, per la realizzazione della funzione strtok_r(), può essere confrontato con quella relativa alla funzione strtok(), per comprendere il senso e l'utilizzo dell'ultimo parametro.
|
Per dimostrare il lavoro della funzione, viene utilizzato lo stesso esempio già usato a proposito di strtok(), con poche piccole modifiche:
|
Avviando il programma si ottiene esattamente la stessa cosa dell'esempio già visto:
strtok: "a" strtok: "??b" strtok: "c" strtok: "(null)" |
Seguono i prototipi delle funzioni descritte nelle sezioni successive. Questi appartengono allo standard C:
|
Il prototipo successivo viene aggiunto dallo standard POSIX:
|
La funzione memset() consente di inizializzare una certa area di memoria con la ripetizione di un certo carattere. Per la precisione, viene usato il valore del parametro c, tradotto in un carattere senza segno, copiandolo per n volte a partire dall'indirizzo a cui punta s. La funzione restituisce s.
|
Per verificare sommariamente il comportamento della funzione si può utilizzare l'esempio seguente:
|
Avviando questo programma in un elaboratore con architettura a 32 bit e inversione dei byte (little endian) si deve ottenere il risultato seguente:
prima: 0x12345678 dopo: 0x1234ffff prima: "ciao amore mio" dopo: "QQQQQamore mio" |
La funzione strerror() serve a tradurre il numero fornito come argomento in un puntatore da cui inizia una stringa contenente una spiegazione. In altri termini, serve a trasformare un numero in una descrizione di un tipo di errore. Qui viene mostrata una soluzione priva di utilità, anche se risponde alle richieste delle specifiche:
|
L'esempio successivo può servire a dimostrare il senso di questa funzione:
|
Utilizzando questo programma compilato con le librerie di un sistema GNU si potrebbero vedere i messaggi seguenti:
Success Operation not permitted No such file or directory No such process Interrupted system call |
La stringa a cui punta la funzione può essere condivisa da altre chiamate successive della stessa, pertanto, in un programma con thread multipli, è possibile che avvenga la sovrascrittura, a meno di disporre di un elenco separato di tutti i tipi di messaggio di errore.
La funzione strerror_r() viene aggiunta dallo standard POSIX e consente di tradurre un errore numerico in stringa, fornendo alla funzione il puntatore iniziale della stringa da produrre e la lunghezza massima che questa può raggiungere, garantendo l'indipendenza tra thread multipli.
Per quanto riguarda l'utilizzo, la differenza fondamentale rispetto a strerror() sta nel fatto che restituisce un valore intero, pari a zero, se l'operazione ha avuto successo, oppure -1 in caso di problemi, aggiornando di conseguenza anche la variabile errno. L'esempio successivo può servire a dimostrare il senso di questa funzione:
|
Utilizzando questo programma compilato con le librerie di un sistema GNU si potrebbero vedere i messaggi seguenti:
Success Operation not permitted No such file or directory No such process Interrupted system call |
La funzione strlen() calcola la lunghezza di una stringa, escludendo dal conteggio il carattere nullo di terminazione:
|
Per verificare sommariamente il comportamento della funzione si può utilizzare l'esempio seguente:
|
Avviando il programma si deve vedere il risultato seguente:
la frase "ciao amore" si compone di 10 caratteri |
Il file signal.h
della libreria standard definisce principalmente delle funzioni per la gestione dei segnali che riguardano il programma. Assieme alle funzioni definisce anche delle macro-variabili per classificare i segnali e per fare riferimento a delle funzioni predefinite, destinate astrattamente al trattamento dei segnali (si veda eventualmente la realizzazione del file signal.h
e di alcune delle sue funzioni nei sorgenti di os32, sezione 95.17).
Dal punto di vista del programmatore, l'uso delle funzioni di questo file di intestazione può essere abbastanza semplice, ma la comprensione di come siano organizzate nel file signal.h
diventa invece difficile.
Nello standard POSIX, la questione dei segnali è particolarmente complessa. Nel capitolo viene considerato solo il fatto che i segnali standard sono in numero maggiore, tralasciando sostanzialmente il resto.
Qui vengono proposti due modi alternativi di scrivere il file signal.h
che dovrebbero essere disponibili presso allegati/c/include/signal.h e allegati/c/include/signal-bis.h.
Per la gestione dei segnali ci sono due funzioni che vengono dichiarate nel file signal.h
: signal() e raise(). La funzione raise() serve ad azionare un segnale, come dire che serve ad attivare manualmente un allarme interno al programma, specificato da un numero particolare che ne definisce il tipo. Il programma contiene sempre una procedura predefinita che stabilisce ciò che deve essere fatto in presenza di un certo allarme, ma il programmatore può ridefinire la procedura attraverso l'uso della funzione signal(), con la quale si associa l'avvio di una funzione particolare in presenza di un certo segnale. Il modello sintattico seguente rappresenta, in modo estremamente semplificato, l'uso della funzione signal():
signal (n_segnale, funzione_da_associare) |
Logicamente la funzione che si associa a un certo numero di segnale viene indicata negli argomenti della chiamata come puntatore a funzione. La funzione che viene passata come argomento è un gestore di segnale e deve avere una certa forma:
void gestore (n_segnale) |
In pratica, quando viene creata l'associazione tra segnale e funzione che deve gestirlo, la funzione in questione deve avere un parametro tale da poter rappresentare il numero del segnale che la riguarda e non restituisce alcun valore (pertanto è di tipo void).
Avendo determinato questo, il modello della funzione signal() può essere precisato un po' di più:
signal (n_segnale, void (*gestore)(int)) |
Ciò significa che il secondo argomento della funzione signal() è un puntatore a una funzione (gestore()) con un parametro di tipo int, la quale non restituisce alcunché (void).
Ma non è ancora stato specificato cosa deve restituire la funzione signal(): un puntatore a una funzione che ha un parametro di tipo int e che a sua volta non restituisce alcunché. In pratica, signal() deve restituire il puntatore a una funzione che ha le stesse caratteristiche di quella del proprio secondo parametro. A questo punto, si arriva al prototipo completo, ma molto difficile da interpretare a prima vista:
void (*signal (n_segnale, void (*gestore)(int)))(int); |
Per ovviare a questo problema di comprensibilità, anche se lo standard non lo prescrive, di norma, nel file signal.h
si dichiara un tipo speciale, in qualità di puntatore a funzione con le caratteristiche del gestore di segnale:
|
Così facendo, la funzione signal() può essere dichiarata in modo più gradevole:
sighandler_t signal (n_segnale, sighandler_t gestore); |
A parte il caso di sighandler_t che non fa parte dello standard del linguaggio, il file include.h
definisce il tipo sig_atomic_t, il cui uso non viene precisato dai documenti ufficiali. Si chiarisce solo che deve trattarsi di un valore intero, possibilmente di tipo volatile, a cui si possa accedere attraverso una sola istruzione elementare del linguaggio macchina (in modo tale che la lettura o la modifica del suo contenuto non possa essere sospesa a metà da un'interruzione di qualunque genere).
|
Nell'esempio, il tipo sig_atomic_t viene dichiarato come equivalente al tipo int, supponendo che l'accesso alla memoria per un tipo intero normale corrisponda a un'operazione «atomica» nel linguaggio macchina. A ogni modo, il tipo a cui corrisponde sig_atomic_t può dipendere da altri fattori, mentre l'unico vincolo nel rango è quello di poter contenere i valori rappresentati dalle macro-variabili SIG..., le quali individuano mnemonicamente i segnali.
Il programmatore che deve memorizzare un segnale in una variabile, potrebbe usare per questo il tipo sig_atomic_t.
Un gruppo di macro-variabili definisce l'elenco dei segnali gestibili. Lo standard del linguaggio ne prescrive solo una quantità minima, mentre il sistema operativo può richiederne degli altri. Teoricamente l'associazione del numero al nome simbolico del segnale è libera, ma in pratica la concordanza con altri standard prescrive il rispetto di un minimo di uniformità.
|
|
Lo standard POSIX prescrive un insieme minimo di segnali più numeroso, attribuendo anche un'azione predefinita a carico del sistema operativo.
|
L'azione predefinita è quella che deve essere svolta dal sistema operativo se il segnale non viene catturato o non viene ignorato dal programma che lo riceve (tenendo conto che per SIGKILL e SIGSTOP i programmi non possono intervenire). Una «terminazione normale» implica la conclusione normale del processo elaborativo, a parte il fatto che il valore restituito dal processo dipende dal segnale stesso; una «terminazione anomala» implica di solito qualcosa di più, di solito si tratta dello scarico della memoria del processo in un file (core dump), per consentire un'analisi di quanto accaduto; la «sospensione» rappresenta un arresto temporaneo, in attesa di un segnale di «continuazione»; un segnale «ignorato» indica che lo stato del processo non viene cambiato, ma ciò non significa che il segnale in sé sia privo di conseguenze.
Il segnale SIGCHLD che formalmente viene indicato come privo di effetti, nella tradizione Unix ha un ruolo molto importante e relativamente complesso, per la gestione della dipendenza dei processi. In un sistema Unix i processi hanno una dipendenza gerarchica, trattata secondo un albero genealogico, dove ogni processo ha un genitore. Dato che la conclusione di un processo produce un valore che dovrebbe essere raccolto dal genitore che lo ha avviato (oppure che lo ha adottato, nel caso il genitore vero sia defunto nel frattempo), quando un processo muore (termina di funzionare per qualunque motivo), il genitore riceve un segnale SIGCHLD: se il genitore è in attesa del valore di uscita del processo defunto, lo raccoglie e le tracce residue di tale processo possono essere distrutte definitivamente; altrimenti, se il segnale non viene catturato il processo defunto viene eliminato senza comunicare tale valore al genitore.
Lo standard prescrive di definire tre macro-variabili che devono espandersi in un puntatore a quel tipo di funzione che deve essere in grado di gestire le azioni da compiere in relazione alla ricezione di un certo segnale. Tuttavia, questo puntatore non deve essere rivolto a una funzione vera, ma averne solo la forma. In pratica, si usano dei valori interi con un valore assoluto molto piccolo e si esegue un cast per trasformarli in puntatori a funzione, come già accennato.
Per ottenere questo risultato, si possono dichiarare le macro-variabili in due modi equivalenti, con la differenza che il secondo è probabilmente più difficile da interpretare:
|
|
Lo standard sottolinea il fatto che il numero trasformato in puntatore non deve poter corrispondere all'indirizzo di alcuna funzione reale; pertanto i valori usati possono essere solo molto bassi o molto alti (in termini di valore assoluto), contando sul fatto che a tali indirizzi non ci possano essere funzioni reali. In pratica, non deve succedere che venga dichiarata una funzione per la gestione di un segnale che finisca per avere proprio tali indirizzi, perché se così fosse, non verrebbe avviata, ma al suo posto verrebbe considerata l'azione che una di queste macro-variabili simboleggia.
|
La funzione signal() viene usata per associare un «gestore di segnale», costituito dal puntatore a una funzione, a un certo segnale; tutto questo allo scopo di attivare automaticamente quella tale funzione al verificarsi di un certo evento che si manifesta tramite un certo segnale.
La funzione signal() restituisce un puntatore alla funzione che precedentemente si doveva occupare di quel segnale. Se invece l'operazione fallisce, signal() esprime questo errore restituendo il valore SIG_ERR, spiegando così il motivo per cui questo debba avere l'apparenza di un puntatore a funzione.
Per la stessa ragione per cui esiste SIG_ERR, le macro-variabili SIG_DFL e SIG_IGN vanno usate come gestori di segnali, rispettivamente, per ottenere il comportamento predefinito o per far sì che i segnali siano ignorati semplicemente.
In linea di principio si può ritenere che nel proprio programma esista una serie iniziale di dichiarazioni implicite per cui si associano tutti i segnali gestibili a SIG_DFL:
|
In base al fatto che sia stata dichiarato o meno il tipo sighandler_t, la funzione potrebbe avere i prototipi seguenti:
|
|
L'altra funzione da considerare è raise(), con la quale si attiva volontariamente un segnale, dal quale poi dovrebbero o potrebbero sortire delle conseguenze, come stabilito in una fase precedente attraverso signal(). La funzione raise() è molto semplice:
|
La funzione richiede come argomento il numero del segnale da attivare e restituisce un valore pari a zero in caso di successo, altrimenti restituisce un valore diverso da zero. Naturalmente, a seconda dell'azione che viene intrapresa all'interno del programma, a seguito della ricezione del segnale, può darsi che dopo questa funzione non venga eseguito altro, pertanto non è detto che possa essere letto il valore che la funzione potrebbe restituire.
Viene proposto un esempio che serve a dimostrare il meccanismo di provocazione e intercettazione dei segnali:
|
All'inizio del programma vengono definite delle funzioni per il trattamento delle situazioni che hanno provocato un certo segnale. Nella funzione main(), prima di ogni altra cosa, si associano tali funzioni ai segnali principali, quindi si passa a un ciclo senza fine, nel quale possono essere provocati dei segnali premendo un certo tasto, come suggerito da un breve menù. Per esempio è possibile provocare la condizione che si verifica tentando di dividere un numero per zero:
[0][Invio] divisione per zero [c][Invio] provoca un segnale SIGINT [t][Invio] provoca un segnale SIGTERM [q][Invio] conclude il funzionamento |
0
[Invio]
Sto per eseguire una divisione per zero: Attenzione: ho intercettato il segnale SIGFPE (8) e devo concludere il funzionamento! |
La divisione per zero fa scattare il segnale SIGFPE che viene intercettato dalla funzione sigfpe_handler(), la quale però non può far molto e così conclude anche il funzionamento del programma.
Attraverso il menù è possibile provocare anche un segnale SIGINT e un segnale SIGTERM, ma per questo è più interessante provare con i mezzi che dovrebbe offrire il sistema operativo:
[0][Invio] divisione per zero [c][Invio] provoca un segnale SIGINT [t][Invio] provoca un segnale SIGTERM [q][Invio] conclude il funzionamento |
[Ctrl c]
[Invio]
Attenzione: ho intercettato il segnale SIGINT (2), però non intendo rispettarlo. |
Utilizzando un sistema operativo Unix o simile, da un altro terminale, o da un'altra console, è possibile inviare un segnale specifico al programma:
$
kill n_processo
[Invio]
Attenzione: ho intercettato il segnale SIGTERM (15), però non intendo rispettarlo. |
$
kill -s 4 n_processo
[Invio]
Ho intercettato il segnale n. 4. |
$
kill -s 11 n_processo
[Invio]
Ho intercettato il segnale n. 11. |
Secondo l'esempio, i segnali 4 e 11 sono, rispettivamente, SIGILL e SIGSEGV.
[q]
[Invio]
Il file time.h
della libreria standard definisce principalmente delle funzioni per il trattamento delle informazioni data-orario. Non è stabilito in che modo venga rappresentato il tempo internamente alle funzioni, anche se di norma si tratta di un valore intero che esprime una quantità di secondi o di frazioni di secondo (si veda eventualmente la realizzazione del file time.h
e di alcune delle sue funzioni nei sorgenti di os32, sezione 95.29).
La funzione clock() consente di ottenere il tempo di utilizzo del microprocessore (CPU), espresso virtualmente in cicli di CPU. In pratica, viene definita la macro-variabile CLOCKS_PER_SEC, contenente il valore che esprime convenzionalmente la quantità di cicli di CPU per secondo; quindi, il valore restituito dalla funzione clock() si traduce in secondi dividendolo per CLOCKS_PER_SEC. Il valore restituito dalla funzione clock() e l'espressione in cui si traduce la macro-variabile CLOCKS_PER_SEC sono di tipo clock_t:
|
La funzione clock() restituisce il tempo di CPU espresso in unità clock_t, utilizzato dal processo elaborativo a partire dall'avvio del programma. Se la funzione non è in grado di dare questa indicazione, allora restituisce il valore -1, o più precisamente (clock_t) (-1).
Per valutare l'intervallo di tempo di utilizzo della CPU, da una certa posizione del programma, a un'altra, occorre memorizzare i valori ottenuti dalla funzione e poi procedere a una sottrazione.
Per comprendere il significato della funzione clock(), del tipo clock_t e della macro-variabile CLOCKS_PER_SEC, viene proposto un esempio molto semplice, ma completo, dove si intende che il tipo clock_t sia intero e sia contenibile in una variabile di tipo long int:
|
Avviando questo programma si potrebbe leggere un risultato simile al testo seguente, dove si vede un valore di CLOCKS_PER_SEC pari a 1 000 000:
Tempo iniziale: 0/1000000 Tempo finale: 20000/1000000 |
Generalmente, nei sistemi Unix si tratta il tempo come una quantità di secondi trascorsi a partire da un'epoca di riferimento, che tradizionalmente coincide con l'ora zero del giorno 1 gennaio 1970. Da questo concetto deriva il tipo time_t della libreria, che, secondo lo standard, rappresenta la quantità di unità di tempo trascorsa a partire da un'epoca di riferimento.
|
Ammesso che si tratti di un numero intero, così come viene ipotizzato dall'esempio proposto, il rango costituisce il limite alle date rappresentabili. Pertanto, se il tipo time_t viene dichiarato come numero intero con segno, a 32 bit, per rappresentare una quantità di secondi (come nella tradizione Unix), significa che si possono rappresentare al massimo 24 855 giorni, pari a circa 68 anni.(8) Se l'epoca di riferimento è il 1970, si può arrivare al massimo al 2038.
La libreria standard prescrive che sia definito il tipo struct tm, con il quale è possibile rappresentare tutte le informazioni relative a un certo tempo, secondo le convenzioni umane. Lo standard prescrive con precisione i membri minimi della struttura e l'intervallo di valori che possono contenere:
|
Si può osservare che il mese viene rappresentato con valori che vanno da 0 a 11, pertanto gennaio si indica con lo zero e dicembre con il numero 11; inoltre, l'intervallo ammesso per i secondi consente di rappresentare un secondo in più, dato che l'intervallo corretto sarebbe da 0 a 59; infine, il fatto che i giorni dell'anno vadano da 0 (il primo) a 365 (l'ultimo), significa che negli anni normali i valori vanno da 0 a 364, mentre negli anni bisestili si arriva a contare fino a 365.
Un gruppo di funzioni dichiarate nel file time.h
ha lo scopo di elaborare in qualche modo le informazioni legate al tempo ed eventualmente di convertirle in formati diversi. Queste funzioni trattano il tempo in forma di variabili di tipo time_t o di tipo struct tm.(9)
La variabile di tipo time_t che viene usata in queste funzioni potrebbe esprimere un valore riferito al tempo universale (UT), mentre le funzioni che la utilizzano dovrebbero tenere conto del fuso orario, in base alle informazioni che può offrire il sistema operativo.
La funzione time() determina il tempo attuale secondo il calendario del sistema operativo, restituendolo nella forma del tipo time_t. La funzione richiede un parametro, costituito da un puntatore di tipo time_t *: se questo puntatore è valido, la stessa informazione che viene restituita viene anche memorizzata nell'indirizzo indicato da tale puntatore.
|
In pratica, se è possibile, l'informazione data-orario raccolta dalla funzione, viene anche memorizzata in *timer.
Se la funzione non può fornire l'informazione richiesta, allora restituisce il valore -1, o più precisamente: (time_t) (-1).
La funzione difftime() calcola la differenza tra due date, espresse in forma time_t e restituisce l'intervallo in secondi, in una variabile in virgola mobile, di tipo double:
|
Per la precisione, viene eseguito time1-time0 e di conseguenza va il segno del risultato.
La funzione mktime() riceve come argomento il puntatore a una variabile strutturata di tipo struct tm, contenente le informazioni sull'ora locale, e determina il valore di quella data secondo la rappresentazione interna, di tipo time_t:
|
La funzione tiene in considerazione solo alcuni membri della struttura; per la precisione, non considera il giorno della settimana e il giorno dell'anno; inoltre, ammette anche valori al di fuori degli intervalli stabiliti per i vari membri della struttura; infine, considera un valore negativo per il membro timeptr->tm_isdst come la richiesta di determinare se sia o meno in vigore l'ora estiva per la data indicata.
Se la funzione non è in grado di restituire un valore rappresentabile nel tipo time_t, o comunque se non può eseguire il suo compito, restituisce il valore -1, o più precisamente (time_t) (-1). Se invece tutto procede regolarmente, la funzione provvede anche a correggere i valori dei vari membri della struttura e a ricalcolare il giorno della settimana e dell'anno.
L'esempio successivo mostra la dichiarazione di una variabile strutturata di tipo struct tm, assegnando ai suoi membri dei valori non corretti. Con l'aiuto della funzione mktime() si ricostruisce la data secondo le convenzioni comuni:
|
Eseguendo questo programma di esempio si dovrebbe ottenere il testo seguente:
2007/6/33 0:0:60 2007/7/3 0:1:0 giorno della settimana: 2 giorno dell'anno: 184 ora estiva: 1 |
Le funzioni gmtime() e localtime() hanno in comune il fatto di ricevere come argomento il puntatore di tipo time_t *, a un'informazione data-orario, per restituire il puntatore a una variabile strutturata di tipo struct tm *. In altri termini, le due funzioni convertono una data espressa nella forma del tipo time_t, in una data suddivisa nella struttura struct tm:
|
Nell'ambito di queste funzioni, è ragionevole supporre che l'informazione di tipo time_t a cui fanno riferimento, sia espressa in termini di tempo universale e che le funzioni stesse abbiano la possibilità di stabilire il fuso orario e la modalità di regolazione dell'ora estiva.
In ogni caso, la differenza tra le due funzioni sta nel fatto che gmtime() traduce il tempo a cui punta il suo argomento in una struttura contenente la data tradotta secondo il tempo coordinato universale, mentre localtime() la traduce secondo l'ora locale.
Va osservato che queste funzioni restituiscono un puntatore a un'area di memoria che può essere sovrascritta da altre chiamate alle stessi funzioni o a funzioni simili.
Un piccolo gruppo di funzioni del file time.h
è destinato alla conversione dei valori data-orario in stringhe, per l'interpretazione umana.
La funzione asctime() converte un'informazione data-orario, espressa nella forma di una struttura struct tm, in una stringa che esprime l'ora locale, usando però una rappresentazione fissa in lingua inglese:
|
In pratica, dal momento che la data e l'orario vanno espressi secondo le convenzioni della lingua inglese, lo standard stesso descrive completamente questa funzione e il listato seguente è tratto letteralmente da tale definizione:
|
La funzione ctime() converte un'informazione data-orario, espressa nella forma del tipo time_t in una stringa che esprime l'ora locale, usando però una rappresentazione fissa in lingua inglese:
|
Il comportamento di questa funzione è tale da generare una stringa analoga a quella della funzione asctime(), tanto che la si potrebbe esprimere così:
|
Oppure, come macroistruzione, così:
|
La funzione strftime() si occupa di interpretare il contenuto di una struttura di tipo struct tm e di tradurlo in un testo, secondo una stringa di composizione libera. In altri termini, questa funzione si comporta in modo simile a printf(), dove l'input è costituito dalla struttura contenente le informazioni data-orario.
|
Dal modello del prototipo della funzione, si vede che questa restituisce un valore numerico di tipo size_t. Questo valore rappresenta la quantità di elementi(10) che sono stati scritti nella stringa di destinazione, rappresentata dal primo parametro. Dal computo di questi elementi è escluso il carattere nullo di terminazione, benché venga comunque aggiunto dalla funzione.
La funzione richiede, nell'ordine: un array di caratteri da utilizzare per comporre il testo; la dimensione massima di questo array; la stringa di composizione, contenente del testo costante e degli specificatori di conversione; il puntatore alla struttura contenente le informazioni data-orario da usare nella conversione.
La funzione termina il proprio lavoro con successo solo se può scrivere nell'array di destinazione il testo composto secondo le indicazioni della stringa di composizione, includendo anche il carattere nullo di terminazione. Se ciò non avviene, il valore restituito dalla funzione è zero e il contenuto dell'array di destinazione è imprecisato.
Il listato successivo mostra un programma completo che dimostra il funzionamento di strftime(). Va osservato che la conversione eseguita da tale funzione è sensibile alla configurazione locale; precisamente dipende dalla categoria LC_TIME:
|
Ecco cosa si potrebbe ottenere eseguendo questo programma:
45: Ciao amore: sono le 09:32 del 27 giugno 2012. |
Nella tabella successiva vengono elencati gli specificatori di conversione principali. Sono ammissibili delle varianti, con l'aggiunta di modificatori, che però non vengono descritte. Per esempio è ammissibile l'uso degli specificatori %Ec e %Od, per indicare rispettivamente una variante di %c e %d.
|
Il file stdio.h
della libreria standard è quello che fornisce le funzioni più importanti e in generale è il più complesso da realizzare, in quanto dipende strettamente dal meccanismo di gestione dei file del sistema operativo (si veda eventualmente la realizzazione del file stdio.h
e di alcune delle sue funzioni nei sorgenti di os32, sezione 95.18). L'elemento più delicato che viene definito qui è il tipo di dati FILE, da cui dipende quasi tutto il resto.
Alle complicazioni che esistevano già alla nascita del linguaggio, nei primi sistemi Unix, si aggiungono attualmente quelle relative alla distinzione tra file di testo e file binari, oltre che quelle relative alla gestione dei caratteri multibyte, per cui la lettura o la scrittura attraverso un flusso di dati deve tenere conto dello stato di completamento di tali informazioni.
Il file stdio.h
definisce le funzioni principali per l'accesso ai file e una serie di funzioni per la lettura e scrittura di dati formattati (si vedano print(), scanf() e altre analoghe), ma altre funzioni realizzate espressamente per caratteri e stringhe estese (formate da elementi wchar_t) si trovano nel file wchar.h
.
I file proposti che si basano sugli esempi del capitolo sono incompleti, in quanto manca la dichiarazione del tipo FILE e del tipo fpos_t.
Il file stdio.h
, oltre a size_t che fa già parte del file stddef.h
, e di va_list che fa già parte del file stdarg.h
, dichiara due tipi di dati a uso specifico per la gestione dei file: FILE e fpos_t, realizzati normalmente attraverso delle strutture.
Il tipo fpos_t serve a rappresentare tutte le informazioni necessarie a specificare univocamente le posizioni interne a un file, per gli scopi delle funzioni fgetpos() e fsetpos(). Il tipo FILE deve poter esprimere tutte le informazioni necessarie a controllare un flusso di file (ovvero le operazioni su un file aperto), in particolare le posizioni correnti, il puntatore alla memoria tampone (buffer), l'indicatore di errore e di fine file.
|
L'organizzazione effettiva delle strutture che costituiscono i tipi fpos_t e FILE dipende strettamente dal sistema operativo (nel contesto particolare della propria architettura); pertanto, per poterne approfondire le caratteristiche, occorre prima uno studio dettagliato delle funzionalità del sistema operativo stesso.
Alcune funzioni aggiunte dallo standard POSIX utilizzano anche il tipo off_t, che è descritto nel file di intestazione sys/types.h
.
Il file stdio.h
dichiara la macro-variabile NULL, come già avviene nel file stddef.h
, assieme ad altre macro-variabili a uso delle funzioni dichiarate al proprio interno. Quelle più semplici sono descritte nella tabella 69.248. L'esempio proposto della dichiarazione di tali macro-variabili è molto approssimativo:
|
La porzione successiva riguarda le estensioni POSIX:
|
|
|
Pur non essendo necessario che sia così, si può ipotizzare che per ogni file che possa essere aperto simultaneamente, sia disponibile un elemento di tipo FILE organizzato in un array. In tal caso, potrebbe essere dichiarato come nell'esempio seguente, già nel file stdio.h
, anche se il nome usato per l'array è puramente indicativo:
|
L'uso della macro-variabile FOPEN_MAX garantisce che siano predisposti esattamente tutti gli elementi necessari alla gestione simultanea del limite di file previsti.
Lo standard del linguaggio C prescrive che i nomi dei flussi standard previsti siano delle macro-variabili, tali da espandersi in espressioni che rappresentino puntatori di tipo FILE *, diretti ai flussi standard rispettivi. Nel caso del compilatore GNU C i puntatori sono già definiti con lo stesso nome dei flussi e, nel file stdio.h
vi si fa riferimento in qualità di variabili esterne (in quanto dichiarate nella libreria precompilata):
|
Diversamente, nell'ipotesi in cui si gestisca un array di elementi FILE, si potrebbe supporre che i primi tre elementi siano usati per i flussi standard e in tal caso le dichiarazioni delle macro-variabili potrebbero essere fatte così:
|
Le funzioni remove() e rename() consentono, rispettivamente di eliminare o di rinominare un file. Il file in questione viene individuato da una stringa, il cui contenuto deve conformarsi alle caratteristiche del sistema operativo. Le due funzioni hanno in comune il fatto di restituire un valore intero (di tipo int), dove il valore zero rappresenta il completamento con successo dell'operazione, mentre un valore differente indica un fallimento.
|
La sintassi per l'uso della funzione remove() è evidente dal suo prototipo, in quanto si attende un solo argomento che è costituito dal nome del file da eliminare; nel caso della funzione rename(), invece, il primo argomento è il nome del file preesistente e il secondo è quello che si vuole attribuirgli.
È importante ribadire che il comportamento delle due funzioni dipende dal sistema operativo. Per esempio, la ridenominazione può provocare la cancellazione di un file preesistente con lo stesso nome che si vorrebbe attribuire a un altro, oppure potrebbe limitarsi a fallire. In un sistema Unix o simile, molto dipende dalla configurazione dei permessi.
Le funzioni tmpfile() e tmpnam() servono per facilitare la creazione di file temporanei. La prima crea automaticamente un file di cui non si conosce il nome e la collocazione, aprendolo in aggiornamento (modalità wb+); la seconda si limita a generare un nome che potrebbe essere usato per creare un file temporaneo:
|
L'uso della funzione tmpfile() è evidente, in quanto non richiede argomenti e restituisce il puntatore al file creato; la seconda richiede l'indicazione di un array di caratteri da poter modificare, restituendo comunque il puntatore all'inizio dello stesso array. In ogni caso va chiarito che il file creato con la funzione tmpfile, una volta chiuso, viene rimosso automaticamente.
Le due funzioni devono essere in grado di poter generare un numero di nomi differente pari almeno al valore rappresentato da TMP_MAX, rimanendo il fatto che non possano essere aperti più di FOPEN_MAX file e che non possono essere generati file con nomi già esistenti.
Se si utilizza la funzione tmpnam(), l'array di caratteri che costituisce il primo argomento (s), viene usato dalla funzione per scriverci il nome del file temporaneo, restituendone poi il puntatore; tale array deve avere una dimensione di almeno L_tmpnam elementi, come si vede nell'esempio seguente:
|
Se la funzione tmpnam() riceve come argomento il puntatore nullo, il nome del file temporaneo viene scritto in un'area di memoria statica che viene sovrascritta a ogni chiamata successiva della funzione stessa.
Entrambe le funzioni, se non possono eseguire il loro compito, restituiscono un puntatore nullo.
Le estensioni POSIX aggiungono anche la funzione tempnam(), la quale ha un comportamento simile a quello di tmpnam(), in quanto non crea il file, ma restituisce il percorso del file che potrebbe essere creato:
|
La funzione tempnam() richiede l'indicazione di una stringa contenente il percorso di una directory, in cui si vuole sia creato un file temporaneo. Se la stringa indica una directory inadatta (perché non esiste o non è accessibile o non gli si può scrivere) oppure si indica il puntatore nullo, si fa riferimento a quanto descritto dalla macro-variabile P_tmpdir. Se anche la directory a cui si riferisce la macro-variabile P_tmpdir dà dei problemi, è possibile che la funzione decida in qualche modo dove sia possibile collocare un file temporaneo.
Il secondo argomento della funzione è una stringa che rappresenta un prefisso da usare per il nome del file temporaneo. Tale prefisso può essere lungo al massimo cinque caratteri. Se si vuole omettere tale prefisso, basta indicare il puntatore nullo.
Se tutto va bene, la funzione alloca dello spazio in memoria per la stringa che deve contenere il percorso di un file temporaneo che si potrebbe creare (ma senza crearlo) e ne restituisce il puntatore. Quando tale informazione non serve più, la memoria allocata può essere liberata con la funzione free(). Se invece la funzione fallisce nel suo compito, restituisce il puntatore nullo e aggiorna la variabile errno.
Le funzioni fopen(), freopen() e fclose(), consentono di aprire e chiudere i file, gestendoli attraverso un puntatore al flusso di file loro associato (stream). Il puntatore in questione è di tipo FILE *.
|
Quando viene aperto un file, gli si associa una variabile strutturata di tipo FILE, contenente tutte le informazioni che servono a gestirne l'accesso. Questa variabile deve rimanere univoca e vi si accede normalmente attraverso un puntatore (FILE *). Dal momento che per il linguaggio C un file aperto è un flusso, la variabile strutturata che contiene le informazioni necessarie a gestirne l'accesso viene identificata come il flusso stesso, pertanto nei prototipi la variabile che contiene il puntatore di tipo FILE * viene denominata generalmente stream.
Dal momento che non è compito del programmatore dichiarare la variabile di tipo FILE, in pratica ci si riferisce al flusso di file sempre solo attraverso un puntatore a quella variabile. Pertanto, è più propriamente il puntatore a tale variabile che rappresenta il flusso di file. |
L'apertura di un file, oltre che l'indicazione del nome del file, richiede di specificare la modalità, ovvero il tipo di accesso che si intende gestire. Sono previste le modalità elencate nella tabella 69.258.
|
La funzione fopen() apre il file indicato come primo argomento (una stringa), con la modalità specificata nel secondo (un'altra stringa), restituendo il puntatore al flusso che consente di accedervi (se l'operazione fallisce, la funzione restituisce il puntatore nullo). La modalità di accesso viene espressa attraverso le sigle elencate nella tabella 69.258.
La funzione freopen() consente di associare un file differente a un flusso già esistente, cambiando anche la modalità di accesso, cosa che viene fatta normalmente per ridirigere i flussi standard. I primi due argomenti della funzione sono gli stessi di fopen(), con l'aggiunta alla fine del puntatore al flusso che si vuole ridirigere. La funzione restituisce il puntatore al flusso ridiretto se l'operazione ha successo, altrimenti produce soltanto il puntatore nullo. Se nel primo argomento, al posto di indicare il nome del file, si mette un puntatore nullo, la chiamata della funzione serve solo per modificare la modalità di accesso a un file già aperto, senza ridirigerne il flusso. Va osservato che il cambiamento della modalità di accesso, in ogni caso, dipende dal sistema operativo e non è detto che si possano applicare tutte le combinazioni.
La funzione fclose() permette di chiudere il flusso indicato come argomento, restituendo un valore numerico pari a zero se l'operazione ha successo, oppure il valore corrispondente alla macro-variabile EOF in caso contrario. La chiusura di un flusso implica la scrittura di dati rimasti in sospeso (in una memoria tampone). Un flusso già chiuso non deve essere chiuso nuovamente.
Dal momento che lo standard POSIX introduce il concetto di descrittore di file, per poter associare un flusso di file a un file già aperto come descrittore, si usa la funzione fdopen(), mentre per fare il contrario, si usa la funzione fileno():
|
La funzione fdopen() richiede l'indicazione del numero del descrittore e della modalità di accesso, la quale deve essere compatibile con quanto già definito a proposito del descrittore stesso. L'associazione tra flusso di file e descrittore comporta inizialmente l'azzeramento dell'indicatore di errore e di quello di fine file; inoltre, se viene chiuso il flusso di file, si ottiene automaticamente la chiusura del descrittore relativo.
La funzione fileno() restituisce il numero di descrittore associato a un flusso di file già aperto. Se però l'operazione fallisce, restituisce il valore -1 e aggiorna la variabile errno.
Le funzioni setvbuf() e setbuf() consentono di attribuire una memoria tampone (buffer) a un certo flusso di dati (un file già aperto), mentre fflush() consente di richiedere espressamente lo scarico della memoria in modo che le operazioni sospese di scrittura siano portate a termine completamente.
|
La funzione setvbuf() permette di attribuire una memoria tampone a un file che è appena stato aperto e per il quale non è ancora stato eseguito alcun accesso. Il primo argomento della funzione è il puntatore al flusso relativo e il secondo è il puntatore all'inizio dell'array di caratteri da usare come memoria tampone. Se al posto del riferimento alla memoria tampone si indica un puntatore nullo, si intende che la funzione debba allocare automaticamente lo spazio necessario; se invece l'array viene fornito, è evidente che deve rimanere disponibile per tutto il tempo in cui il flusso rimane aperto.
Il terzo argomento atteso dalla funzione setvbuf() è un numero che esprime la modalità di funzionamento della memoria tampone. Questo numero viene fornito attraverso l'indicazione di una tra le macro-variabili _IOFBF, _IOLBF e _IONBF. Il quarto argomento indica la dimensione dell'array da usare come memoria tampone: se l'array viene fornito effettivamente, si tratta della dimensione che può essere utilizzata; altrimenti è la dimensione richiesta per l'allocazione automatica.
La funzione setvbuf() restituisce zero se l'operazione richiesta è eseguita con successo; in caso contrario restituisce un valore differente.
La funzione setbuf() è una semplificazione di setvbuf() che non restituisce alcun valore, dove al posto di indicare la modalità di gestione della memoria tampone, si intende implicitamente quella corrispondente alla macro-variabile _IOFBF (pertanto si tratta di una gestione completa della memoria tampone), mentre al posto di indicare la dimensione dell'array che costituisce la memoria tampone si intende il valore corrispondente alla macro-variabile BUFSIZ. In pratica, è come utilizzare la funzione setvbuf() così:
(void) setvbuf (stream, buffer, _IOFBF, BUFSIZ); |
La funzione fflush si usa per i file aperti in scrittura, allo scopo di aggiornare i file se ci sono dati sospesi nella memoria tampone che devono ancora essere trasferiti effettivamente. La funzione si attende come argomento il puntatore al flusso per il quale eseguire questo aggiornamento, ma se si fornisce il puntatore nullo (la macro-variabile NULL), si ottiene l'aggiornamento di tutti i file aperti in scrittura. A parte questo, la funzione non altera lo stato del flusso.
La funzione fflush() restituisce zero se riesce a completare con successo il proprio compito, altrimenti restituisce il valore corrispondente a EOF e aggiorna la variabile individuata dall'espressione errno in modo da poter risalire al tipo di errore che si è presentato.
La funzione fflush() interviene solo nella memoria tampone gestita internamente al programma, ma bisogna tenere presente che il sistema operativo potrebbe gestire un'altra memoria del genere, per il cui scarico occorre eventualmente intervenire con funzioni specifiche del sistema stesso. |
Alcune funzioni del file stdio.h
sono realizzate con lo scopo principale di comporre una stringa attraverso l'inserzione di componenti di vario genere, convertendo i dati in modo da poterli rappresentare in forma «tipografica», nel senso di sequenza di caratteri che hanno una rappresentazione grafica.
Queste funzioni hanno in comune una stringa contenente degli specificatori di conversione, caratterizzati dal fatto che iniziano con il simbolo di percentuale (%) e dalla presenza di un elenco indefinito di argomenti, il cui valore viene utilizzato in sostituzione degli specificatori di conversione. Il modo in cui si esprime uno specificatore di conversione può essere complesso, pertanto viene mostrato un modello sintattico che descrive la sua struttura:
%[simbolo][n_ampiezza][.n_precisione][hh|h|l|ll|j|z|t|L]tipo |
La prima cosa da individuare in uno specificatore di conversione è il tipo di argomento che viene interpretato e, di conseguenza, il genere di rappresentazione che se ne vuole produrre. Il tipo viene espresso da una lettera alfabetica, alla fine dello specificatore di conversione. La tabella successiva riepiloga i tipi principali.
|
Nel modello sintattico che descrive lo specificatore di conversione, si vede che subito dopo il segno di percentuale può apparire un simbolo (flag). I simboli principali che possono essere utilizzati sono descritti nella tabella successiva.
|
Subito prima della lettera che definisce il tipo di conversione, possono apparire una o due lettere che modificano la lunghezza del valore da interpretare (per lunghezza si intende qui la quantità di byte usati per rappresentarlo). Per esempio, %...Lf indica che la conversione riguarda un valore di tipo long double. Tra questi specificatori della lunghezza del dato in ingresso ce ne sono alcuni che indicano un rango inferiore a quello di int, come per esempio %...hhd che si riferisce a un numero intero della dimensione di un signed char; in questi casi occorre comunque considerare che nella trasmissione degli argomenti alle funzioni interviene sempre la promozione a intero, pertanto viene letto il dato della dimensione specificata, ma viene «consumato» il risultato ottenuto dalla promozione. La tabella successiva riepiloga i modificatori di lunghezza principali.
|
I modificatori di lunghezza si possono utilizzare anche con il tipo %...n. In tal caso, si intende che il puntatore sia del tipo specificato dalla lunghezza. Per esempio, %tn richiede di memorizzare la quantità di byte composta fino a quel punto in una variabile di tipo ptrdiff_t, a cui si accede tramite il puntatore fornito.
Tra il simbolo (flag) e il modificatore di lunghezza può apparire un numero che rappresenta l'ampiezza da usare nella trasformazione ed eventualmente la precisione: ampiezza[.precisione]. Il concetto parte dalla rappresentazione dei valori in virgola mobile, dove l'ampiezza indica la quantità complessiva di caratteri da usare e la precisione indica quanti di quei caratteri usare per il punto decimale e le cifre successive, ma si applica anche alle stringhe.
In generale, per quanto riguarda la rappresentazione di valori numerici, la parte intera viene sempre espressa in modo completo, anche se l'ampiezza indicata è inferiore; ai numeri interi la precisione non si applica; per i numeri in virgola mobile con rappresentazione esponenziale, la precisione riguarda le cifre decimali che precedono l'esponente; per le stringhe la precisione specifica la quantità di caratteri da considerare, troncando il resto.
In un altro capitolo, la tabella 67.26 riporta un elenco di esempi di utilizzo della funzione printf() dove si può valutare l'effetto dell'indicazione dell'ampiezza e della precisione.
L'ampiezza, o la precisione, o entrambe, potrebbero essere indicate da un asterisco, come per esempio %*.*f. L'asterisco usato in questo modo indica che il valore corrispondente (ampiezza, precisione o entrambe) viene tratto dagli argomenti come intero (int). Pertanto, per tornare all'esempio composto come %*.*f, dagli argomenti viene prelevato un intero che rappresenta l'ampiezza, un altro intero che rappresenta la precisione, quindi si preleva un valore double che è quanto va rappresentato secondo l'ampiezza e la precisione richieste.
Un gruppo di funzioni per la composizione dell'output riceve direttamente gli argomenti variabili che servono agli specificatori di conversione:
|
Tutte le funzioni di questo gruppo hanno in comune la stringa di composizione, costituita dal parametro format, e gli argomenti successivi che sono in quantità e qualità indeterminata, in quanto per la loro interpretazione contano gli specificatori di conversione inseriti nella stringa di composizione. Inoltre, tutte queste funzioni restituiscono la quantità di caratteri prodotti dall'elaborazione della stringa di composizione. Va osservato che il conteggio riguarda solo i caratteri e non include, eventualmente, il carattere nullo di terminazione di stringa che viene usato per le funzioni sprintf() e snprintf(). Se durante il procedimento di composizione si verifica un errore, queste funzioni possono restituire un valore negativo.
La funzione sprintf() produce il risultato della composizione memorizzandolo a partire dal puntatore indicato come primo parametro (s) e aggiungendo il carattere nullo di terminazione. La funzione snprintf(), invece, produce al massimo n-1 caratteri, aggiungendo sempre il carattere nullo di terminazione.
La funzione fprintf() scrive il risultato della composizione attraverso il flusso di file stream, mentre printf() lo scrive attraverso lo standard output.
A fianco delle funzioni descritte nella sezione precedente, un gruppo analogo svolge le stesse operazioni, ma ricevendo gli argomenti variabili per riferimento. In pratica si tratta di ciò che serve quando gli argomenti variabili sono stati ottenuti da un'altra funzione e non da una chiamata diretta.
|
Il funzionamento è conforme a quello delle funzioni che non hanno la lettera v iniziale; per esempio, vsprintf() si comporta conformemente a sprintf(). Per comprendere la differenza si potrebbe dimostrare la realizzazione ipotetica della funzione printf() avvalendosi di vprintf():
|
Un piccolo gruppo di funzioni del file stdio.h
è specializzato nell'interpretazione di una stringa, dalla quale si vanno a estrapolare dei componenti da collocare in variabili di tipo opportuno. In altri termini, da una stringa che rappresenta un valore espresso attraverso caratteri grafici, si vuole estrarre il valore e assegnare a una certa variabile.
Il meccanismo è opposto a quello usato dalle funzioni del tipo ...printf() e anche in questo caso si parte da una stringa contenente principalmente degli specificatori di conversione, seguita da un numero indefinito di argomenti. Gli specificatori delle funzioni che interpretano l'input sono simili a quelli usati per la composizione dell'output, ma non possono essere equivalenti in tutto. Sinteticamente si possono descrivere così:
%[*][n_ampiezza][hh|h|l|ll|j|z|t|L]tipo |
Come si può vedere, all'inizio può apparire un asterisco, il cui scopo è quello di annullare l'assegnamento del valore a una variabile. In pratica, con l'asterisco il dato corrispondente allo specificatore viene interpretato, ma poi non viene salvato.
Successivamente può apparire un numero che rappresenta l'ampiezza del dato da interpretare, in byte, il cui scopo è quello di limitare la lettura fino a un certo carattere (inteso come char, pertanto le sequenze multibyte contano per più di una unità singola).
Dopo può apparire una sigla, composta da una o più lettere, il cui scopo è quello di modificare la dimensione predefinita della variabile di destinazione. In altri termini, senza questo modificatore si intende che la variabile ricevente debba essere di una certa grandezza, ma con l'aggiunta del «modificatore di lunghezza» si precisa invece qualcosa di diverso. In pratica, il modificatore di lunghezza usato da queste funzioni è equivalente a quello delle funzioni di composizione dell'output.
Al termine dello specificatore di conversione appare una lettera che dichiara come deve essere interpretato il dato in ingresso e, in mancanza del modificatore di lunghezza, indica anche la dimensione della variabile ricevente.
|
|
A proposito dell'interpretazione di caratteri e di stringhe, va precisato cosa accade quando si usa il modificatore l (elle). Se nello specificatore di conversione appare un valore numerico che esprime un'ampiezza, questa indica una quantità di caratteri, in ingresso, da intendersi come byte. Utilizzando gli specificatori %...lc e %...ls, la quantità di questi caratteri continua a riferirsi a byte, ma si interpretano le sequenze multibyte in ingresso per generare caratteri di tipo wchar_t.
Il documento che descrive lo standard del linguaggio afferma che la stringa di conversione è composta da direttive, ognuna delle quali è formata da: uno o più spazi (spazi veri e propri o caratteri di tabulazione orizzontale); un carattere multibyte diverso da % e diverso dai caratteri che rappresentano spazi, oppure uno specificatore di conversione.
[spazi]carattere_multibyte|%... |
Dalla sequenza multibyte che costituisce i dati in ingresso da interpretare, vengono eliminati automaticamente gli spazi iniziali e finali (tutto ciò che si può considerare spazio, anche il codice di interruzione di riga), quando all'inizio o alla fine non ci sono corrispondenze con specificatori di conversione che possono interpretarli.
Quando la direttiva di interpretazione inizia con uno o più spazi orizzontali, significa che si vogliono ignorare gli spazi a partire dalla posizione corrente nella lettura dei dati in ingresso; inoltre, la presenza di un carattere che non fa parte di uno specificatore di conversione indica che quello stesso carattere deve essere incontrato nell'interpretazione dei dati in ingresso, altrimenti il procedimento di lettura e valutazione si deve interrompere. Se due specificatori di conversione appaiono adiacenti, i dati in ingresso corrispondenti possono essere separati da spazi orizzontali o da spazi verticali (il codice di interruzione di riga).
Purtroppo, la sintassi per la scrittura delle stringhe di conversione non è molto soddisfacente e diventa difficile spiegarne il comportamento, a meno di rimanere fermi su esempi molto semplici. |
Un gruppo di funzioni per l'interpretazione dell'input riceve direttamente gli argomenti variabili che servono agli specificatori di conversione:
|
Tutte le funzioni di questo gruppo hanno in comune la stringa di conversione, costituita dal parametro format, e gli argomenti successivi che sono puntatori di tipo indeterminato, in quanto per la loro interpretazione contano gli specificatori di conversione inseriti nella stringa. Inoltre, tutte queste funzioni restituiscono la quantità di valori assegnati alle variabili rispettive, oppure il valore corrispondente alla macro-variabile EOF nel caso si verifichi un errore prima di qualunque conversione.
La funzione sscanf() scandisce il contenuto della stringa indicata come primo parametro (s); la funzione fscanf() scandisce l'input proveniente dal flusso di file indicato come primo argomento (stream), mentre la funzione scanf() scandisce direttamente lo standard input.
A fianco delle funzioni descritte nella sezione precedente, un gruppo analogo svolge le stesse operazioni, ma ricevendo gli argomenti variabili per riferimento. In pratica si tratta di ciò che serve quando gli argomenti variabili sono stati ottenuti da un'altra funzione e non da una chiamata diretta.
|
Il funzionamento è conforme a quello delle funzioni che non hanno la lettera v iniziale; per esempio, vscanf() si comporta conformemente a scanf(). Per comprendere la differenza si potrebbe dimostrare la realizzazione ipotetica della funzione scanf() avvalendosi di vscanf():
|
Le funzioni fgetc() e getc() leggono un carattere (char) attraverso il flusso di file indicato come argomento:
|
Lo standard prescrive che la funzione getc() sia in realtà una macroistruzione, così come si ipotizza nella dichiarazione appena mostrata. A questo proposito occorre tenere presente che, se si usa getc(), l'espressione usata per individuare il flusso di file potrebbe essere valutata più di una volta.
Il carattere letto da fgetc() viene interpretato senza segno e trasformato in un intero (pertanto deve risultare essere di segno positivo). Se viene tentata la lettura oltre la fine del file, la funzione restituisce il valore rappresentato da EOF e memorizza questa condizione nella variabile strutturata che rappresenta il flusso di file. Se invece si verifica un errore di lettura, viene impostato il contenuto dell'indicatore di errore relativo al flusso di file e la funzione restituisce sempre il valore EOF.
Secondo lo standard, la funzione getchar() è equivalente a getc (stdin), senza specificare altro. Ciò può significare ragionevolmente che se getc() è una macroistruzione, anche getchar() dovrebbe esserlo, altrimenti potrebbe trattarsi di una funzione vera e propria:
|
La funzione ungetc() ha lo scopo di annullare l'effetto della lettura dell'ultimo carattere, ma il modo in cui viene gestita la cosa rende la questione molto delicata:
|
Semplificando il problema, la funzione ungetc() rimanda indietro il carattere c nel flusso di file stream dal quale è appena stata eseguita una lettura. Tuttavia, non è garantito che il carattere in questione sia effettivamente quello che è stato letto per ultimo, ma la fase successiva di lettura deve fornire per primo tale carattere.
Si comprende intuitivamente che, se si eseguono operazioni di spostamento della posizione corrente relativa al flusso di file in questione, il carattere rimandato indietro con la funzione ungetc() debba essere dimenticato, soprattutto se questo non corrispondeva a quello che effettivamente era stato letto per ultimo in quel momento.
L'uso della funzione ungetc() implica un aggiornamento della posizione corrente relativa al flusso di file, ma questa modifica, in presenza di file di testo che non siano realizzati secondo lo standard tradizionale dei sistemi Unix, implica che l'entità di questa modifica non possa essere predeterminabile.(11)
La funzione ungetc() può fallire nel suo intento e lo standard prescrive che sia «garantita» la possibilità di rimandare indietro almeno un carattere. Se la funzione riesce a eseguire l'operazione, restituisce il valore positivo corrispondente al carattere rinviato; altrimenti restituisce il valore della macro-variabile EOF.
Le funzioni fputc(), putc() e putchar() eseguono l'operazione inversa, rispettivamente, di fgetc(), getc() e getchar(); anche in questo caso vale il fatto che putc() possa essere realizzata come macroistruzione:
|
La funzione fputc() scrive un carattere (fornito come numero intero positivo) attraverso il flusso di file indicato; putc() fa lo stesso, ma potrebbe essere una macroistruzione; putchar() scrive attraverso lo standard output.
Se la scrittura fallisce, le funzioni (o le macroistruzioni) restituiscono il valore EOF; diversamente restituiscono il valore positivo corrispondente al carattere scritto.
Per come sono state proposte queste funzioni, non c'è differenza nell'uso di getc() al posto di fgetc(), così come tra putc() e fputc(). Evidentemente, se la propria libreria può esprimere le macroistruzioni getc() e putc() richiamando funzioni del sistema operativo (funzioni che dovrebbero essere richiamate anche da fgetc() e fputc()), si può risparmiare un livello di chiamate per accelerare leggermente l'esecuzione del programma. |
Le funzioni fgets() e fputs() sono utili per l'accesso a file di testo, quando si vuole indicare il flusso di file:
|
La funzione fgets() legge al massimo n-1 caratteri (nel senso di elementi char) attraverso il flusso di file stream, copiandoli in memoria a partire dall'indirizzo s e aggiungendo alla fine il carattere nullo di terminazione delle stringhe. La lettura si esaurisce prima di n-1 caratteri se viene incontrato il codice di interruzione di riga, il quale viene rappresentato nella stringa a cui punta s, ovvero se si raggiunge la fine del file. In ogni caso, la stringa s viene terminata correttamente con il carattere nullo.
La funzione fgets() restituisce la stringa s se la lettura avviene con successo, ovvero se ha prodotto almeno un carattere; altrimenti, il contenuto dell'array a cui punta s non viene modificato e la funzione restituisce il puntatore nullo. Se si creano errori imprevisti, la funzione potrebbe restituire il puntatore nullo, ma senza garantire che l'array s sia rimasto intatto.
La funzione fputs() serve a copiare la stringa a cui punta s nel file rappresentato dal flusso di file stream. La copia della stringa avviene escludendo però il carattere nullo di terminazione. Va osservato che questa funzione, pur essendo contrapposta evidentemente a fgets(), non conclude la riga del file, ovvero, non aggiunge il codice di interruzione di riga. Per ottenere la conclusione della riga di un file di testo, occorre inserire nella stringa, espressamente, il carattere \n.
La funzione fputs() restituisce il valore rappresentato da EOF se l'operazione di scrittura produce un errore; altrimenti restituisce un valore positivo qualunque.
Le funzioni gets() e puts() sono utili per l'accesso a file di testo, quando si vogliono utilizzare i flussi standard. In linea di massima, assomigliano a fgets() e fputs(), ma il funzionamento non è perfettamente conforme a quelle:
|
Il funzionamento di gets() è perfettamente conforme a quello di fgets(), con la sola differenza che il flusso di file da cui si leggono i caratteri è lo standard input. Nel caso di puts(), a parte il fatto che si usa lo standard output per la scrittura, occorre sottolineare che alla fine della stringa viene accodata la scrittura del codice di interruzione di riga.
Le funzioni fread() e fwrite() consentono di leggere e scrivere attraverso un flusso di file aperto, il quale deve essere specificato espressamente tra gli argomenti. Lo standard prescrive che queste funzioni si avvalgano rispettivamente di fgetc() e di fputc().
|
Le due funzioni (fread() e fwrite()) hanno praticamente gli stessi argomenti, usati in modo analogo. La lettura e la scrittura avviene a blocchi da size byte, ripetuta per nmemb volte, attraverso il flusso di file specificato come stream. La lettura implica la memorizzazione dei caratteri in forma di elementi unsigned char, a partire dall'indirizzo indicato dal puntatore ptr; la scrittura copia nello stesso modo i caratteri a partire dal puntatore ptr, verso il flusso di file.
L'aggiornamento della posizione corrente interna al file a cui si riferisce il flusso avviene esattamente come per le funzioni fgetc() e fputc().
Il valore restituito dalle funzioni fread() e fwrite() rappresenta la quantità di blocchi, ovvero la quantità di elementi nmemb che sono stati copiati con successo. Pertanto, se si ottiene un valore inferiore a nmemb, significa che l'operazione è stata interrotta a causa di un errore.
Sono previste diverse funzioni per modificare la posizione corrente dei flussi di file. Le funzioni più semplici per iniziare sono fseek(), ftell() e rewind():
|
Lo standard POSIX prevede anche le funzioni fseeko() e ftello(), equivalenti alle funzioni fseek() e ftell() dello standard C, con la differenza che lo scostamento, fornito come argomento o restituito dalla funzione, è di tipo off_t, al posto di essere di tipo long int:
|
In generale, le funzioni fseek() e fseeko() spostano la posizione corrente relativa al flusso di file stream, nella nuova posizione determinata dai parametri whence e offset. Il parametro whence viene fornito attraverso una macro-variabile che può essere SEEK_SET, SEEK_CUR o SEEK_END, indicando rispettivamente l'inizio del file, la posizione corrente o la fine del file. Dalla posizione indicata dal parametro whence viene aggiunta, algebricamente, la quantità di byte indicata dal parametro offset.
Quanto descritto a proposito del posizionamento con le funzioni fseek() e fseeko() riguarda i file che vengono gestiti in modo binario, perché con i file di testo è opportuno avere maggiore accortezza: il valore del parametro offset deve essere zero, oppure quanto restituito in precedenza dalle funzioni ftell() o ftello() per lo stesso flusso di file, ma in tal caso, ovviamente, il parametro whence deve corrispondere a SEEK_SET.
Le funzioni fseek() e fseeko() restituiscono zero se possono eseguire l'operazione, altrimenti danno un risultato diverso; nel caso di fseeko(), quando si presenta un errore, il risultato restituito è precisamente -1, e in più viene aggiornata anche la variabile errno.
Le funzioni ftell() e ftello() restituiscono la posizione corrente del flusso di file indicato come argomento. Questo valore può essere usato con fseek() o fseeko() rispettivamente, al posto dello scostamento (il parametro offset), indicando come posizione di riferimento l'inizio del file, ovvero SEEK_SET. Se le funzioni ftell() e ftello() non riescono a fornire la posizione, restituiscono il valore -1 (tradotto rispettivamente in long int e off_t) e annotano il fatto nella variabile errno.
La funzione rewind() si limita a riposizionare il flusso di file all'inizio. In pratica è come utilizzare la funzione fseek() specificando uno scostamento pari a zero a partire da SEEK_SET, ignorando il valore restituito:
(void) fseek (stream, 0L, SEEK_SET) |
Va osservato che il riposizionamento di un flusso di file implica l'azzeramento dell'indicatore di fine file, se questo risulta impostato, e la cancellazione dei caratteri che eventualmente fossero stati rimandati indietro con la funzione ungetc().
Le funzioni fseek() e ftell() sono utili particolarmente per i file binari ed eventualmente per i file di testo con una rappresentazione dei caratteri tradizionale. Ma quando il file di testo contiene anche caratteri espressi attraverso sequenze multibyte, il posizionamento al suo interno dovrebbe tenere anche conto del progresso nell'interpretazione di queste sequenze. Pertanto, esistono altre due funzioni per leggere la posizione e ripristinarla in un secondo momento:
|
Entrambe le funzioni che appaiono nei due prototipi restituiscono zero se l'operazione è stata compiuta con successo, altrimenti restituiscono un valore differente. Nel caso particolare di fsetpos(), se si verifica un errore, questo viene annotato nella variabile errno.
Le due funzioni richiedono come primo argomento il flusso di file a cui ci si riferisce; come secondo argomento richiedono il puntatore a una variabile di tipo fpos_t. La funzione fgetpos() memorizza nella variabile a cui punta il parametro pos le informazioni sulla posizione corrente del file, assieme allo stato di interpretazione relativo alle sequenze multibyte; la funzione fsetpos(), per converso, utilizza la variabile a cui punta pos per ripristinare la posizione memorizzata, assieme allo stato di avanzamento dell'interpretazione di una sequenza multibyte.
Lo standard POSIX, introducendo la gestione dei thread multipli, inserisce nel file stdio.h
alcune funzioni per controllare l'accesso esclusivo ai flussi di file. Va tenuto a mente che si tratta di un controllo che riguarda esclusivamente il processo elaborativo in corso, e non l'accesso ai file da parte di processi differenti.
|
La funzione flockfile() cerca di ottenere un accesso esclusivo a un file, individuato da un puntatore che rappresenta il flusso di file relativo. Se il file risulta già impegnato, rimane in attesa, fino a quando si libera. La funzione trylockfile(), invece, non rimane in attesa e restituisce l'esito della sua azione: zero se è riuscita a ottenere l'accesso esclusivo, un numero diverso se invece non c'è riuscita. Quando poi un thread che aveva ottenuto l'accesso esclusivo a un flusso di file, non ne ha più bisogno, lo libera con la funzione funlockfile().
Le funzioni getc(), putc(), getchar() e putchar(), quando sono presenti le estensioni per la gestione dei thread multipli, e di conseguenza anche per l'accesso esclusivo ai flussi di file, si comportano rispettando tali vincoli. Eventualmente, se per qualche ragione si vogliono usare queste funzionalità, ignorando espressamente tali vincoli, sono disponibili funzioni equivalenti, il cui nome termina per _unlocked:
|
Lo standard POSIX prescrive comunque che queste siano usate solo da thread che hanno già ottenuto un accesso esclusivo al flusso relativo, in modo da non compromettere la gestione controllata di tali accessi. Pertanto, in tal modo queste funzioni consentono semplicemente un'esecuzione più rapida, dal momento che non vengono eseguiti tutti i controlli necessari.
Lo standard POSIX introduce il concetto di «condotto», ovvero di pipe, attraverso il quale è possibile inviare lo standard output di un processo elaborativo, verso lo standard input di un altro. Per attuare questo meccanismo, è necessario che un processo sia in grado di avviare un altro processo, attraverso un comando da dare alla shell, e da questo processo viene poi letto lo standard output o scritto lo standard input.
Dal punto di vista del processo che crea il condotto, il processo secondario avviato è trattato come se fosse un file, dove però si può solo leggere o scrivere, ma non si possono fare entrambe le cose.
|
Il condotto viene aperto con la funzione popen(), la quale assomiglia a fopen(), ma invece dell'indicazione del percorso del file da aprire, richiede il comando da eseguire attraverso la shell /bin/sh
(nota come la shell POSIX). Per quanto riguarda il tipo di accesso, va osservato che può trattarsi soltanto di lettura o scrittura, pertanto si può scegliere solo tra la stringa "r" o "w".
La lettura o la scrittura in un flusso di file associato a un condotto avviene nel modo consueto, ma la chiusura richiede l'uso della funzione pclose().
Lo standard POSIX prevede che il sistema operativo abbia una gestione dei file di dispositivo per rappresentare i vari componenti fisici dell'elaboratore. Nel file stdio.h
inserisce la funzione ctermid(), con lo scopo di conoscere il percorso del file di dispositivo del terminale associato al processo elaborativo.
|
La funzione si aspetta di ricevere come argomento il puntatore a una stringa modificabile, in cui scrivere il percorso del terminale. Se però viene fornito un puntatore nullo, l'informazione viene annotata in un'area di memoria che può essere statica (e quindi riutilizzata a ogni chiamata della funzione). Il percorso del terminale, annotato dalla funzione, può utilizzare al massimo la quantità di caratteri definita dalla macro-variabile L_ctermid; pertanto, se si definisce un array di caratteri da usare per tale annotazione, deve essere almeno quella dimensione.
La funzione restituisce sempre il puntatore a una stringa, che può essere nulla se non è possibile determinare il terminale. Pertanto la funzione non prevede l'indicazione di errori.
Un gruppo di funzioni di stdio.h
consente di verificare ed eventualmente azzerare lo stato degli indicatori di errore riferiti a un certo flusso di file:
|
La funzione clearerr() azzera gli indicatori di errore e di fine file per il flusso di file indicato come argomento, senza restituire alcunché.
La funzione feof() controlla lo stato dell'indicatore di fine file per il flusso di file indicato. Se questo non è attivo restituisce zero, altrimenti restituisce un valore diverso da zero.
La funzione ferror() controlla lo stato dell'indicatore di errore per il flusso di file indicato. Se questo non è attivo restituisce zero, altrimenti restituisce un valore diverso da zero.
La funzione perror() prende in considerazione la variabile errno e cerca di tradurla in un messaggio testuale da emettere attraverso lo standard error (con tanto di terminazione della riga, in modo da riposizionare a capo il cursore). Se il parametro s corrisponde a una stringa non vuota, il testo di questa viene posto anteriormente al messaggio, separandolo con due punti e uno spazio (: ). Il contenuto del messaggio è lo stesso che si otterrebbe con la funzione strerror(), fornendo come argomento la variabile errno.
Le funzioni per la composizione dell'output che possono essere realizzate senza avere definito la gestione dei file, sono quelle che si limitano a produrre una stringa. La funzione che va realizzata per prima è vsnprintf(), in quanto snprintf() si può limitare a richiamarla. Naturalmente, anche vsprintf() e sprintf() possono avvalersi della stessa vsnprintf(), ponendo un limite massimo abbastanza grande alla stringa da generare. Nella sezione 95.18.42 è disponibile un esempio di realizzazione parziale di vsnprintf(). L'esempio seguente mostra come si ottiene snprintf(), una volta che è disponibile vsnprintf():
|
Eventualmente, per realizzare le funzioni vsprintf() e sprintf(), secondo le limitazioni già descritte, sono sufficienti due macroistruzioni:
|
Wikipedia, C standard library, http://en.wikipedia.org/wiki/C_standard_library
ISO/IEC 9899:TC2, http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
Wikipedia, assert.h, limits.h, stdint.h, errno.h, locale.h, ctype.h, stdarg.h, stdlib.h, inttypes.h, iso646.h, stdbool.h, stddef.h, string.h, signal.h, time.h, stdio.h
http://en.wikipedia.org/wiki/Assert.h, http://en.wikipedia.org/wiki/Limits.h, http://en.wikipedia.org/wiki/Stdint.h, http://en.wikipedia.org/wiki/Errno.h, http://en.wikipedia.org/wiki/Locale.h, http://en.wikipedia.org/wiki/Ctype.h, http://en.wikipedia.org/wiki/Stdarg.h, http://en.wikipedia.org/wiki/Stdlib.h, http://en.wikipedia.org/wiki/Inttypes.h, http://en.wikipedia.org/wiki/Iso646.h, http://en.wikipedia.org/wiki/Stdbool.h, http://en.wikipedia.org/wiki/Stddef.h, http://en.wikipedia.org/wiki/String.h, http://en.wikipedia.org/wiki/Signal.h, http://en.wikipedia.org/wiki/Time.h, http://en.wikipedia.org/wiki/Stdio.h
The Open Group, The Single UNIX® Specification, Version 2, assert.h, limits.h, stdint.h, errno.h, locale.h, ctype.h, stdarg.h, stdlib.h, inttypes.h, iso646.h, stdbool.h, stddef.h, string.h, signal.h, time.h, stdio.h
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/assert.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/limits.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/stdint.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/errno.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/locale.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/ctype.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/stdarg.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/stdlib.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/inttypes.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/iso646.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/stdbool.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/stddef.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/string.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/signal.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/time.h.html,
http://pubs.opengroup.org/onlinepubs/000095399/basedefs/stdio.h.html
Steven Pemberton, Enquire: Everything you wanted to know about your C Compiler and Machine, but didn't know who to ask, http://homepages.cwi.nl/~steven/enquire.html
1) Il carattere viene convertito da unsigned char a int.
2) Si tratta di un puntatore di puntatore, solo perché si deve poter alterare ciò a cui punta, ma questo tipo di valore è, a sua volta, un puntatore.
3) Il tipo size_t, restituito dalle funzioni, è un intero senza segno; pertanto, in condizioni normali, ovvero con una rappresentazione dei valori negativi con il complemento a due, la conversione di -1 si traduce nel valore massimo rappresentabile.
4) Lo standard POSIX non estende il contenuto del file inttypes.h
.
5) Lo standard POSIX non estende il file iso646.h
.
6) Lo standard POSIX non estende il file stdbool.h
7) Lo standard POSIX non estende il file stddef.h
.
8) Il valore positivo massimo è (231)-1, il quale, diviso per la quantità di secondi di un giorno (86 400) dà 24 855 che, diviso 365, dà circa 68 anni.
9) Il caso della funzione clock() e del tipo clock_t è stato considerato a parte.
10) Si tratta di byte: se il testo copiato è costituito da sequenze multibyte, i byte sono in quantità maggiore rispetto ai caratteri tipografici che si ottengono.
11) L'arretramento di un carattere nella posizione corrente di un file di testo non è detto corrisponda alla sottrazione di una unità, perché bisogna tenere in considerazione il modo in cui un file di testo è strutturato nel proprio sistema operativo.
«a2» 2013.11.11 --- Copyright © Daniele Giacomini -- appunti2@gmail.com http://informaticalibera.net