C/Przenośność programów: Różnice pomiędzy wersjami

Pokolorowano kod
(Pokolorowano kod)
Jednak możemy posiadać implementację, która nie posiada tego pliku nagłówkowego. W takiej sytuacji nie pozostaje nam nic innego jak tworzyć własny plik nagłówkowy, w którym za pomocą słówka '''typedef''' sami zdefiniujemy potrzebne nam typy. Np.:
 
<source lang="c">
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned long long u64;
typedef signed long long s64;
</source>
 
Aczkolwiek należy pamiętać, że taki plik będzie trzeba pisać od nowa dla każdej architektury na jakiej chcemy kompilować nasz program.
 
Przykładem może być komunikacja sieciowa, w której przyjęło się, że dane przesyłane są w porządku big-endian. Aby móc łatwo operować na takich danych, w standardzie POSIX zdefiniowano następujące funkcje (w zasadzie zazwyczaj są to makra):
<source lang="c">
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
</source>
 
Pierwsze dwie konwertują liczbę z reprezentacji lokalnej na reprezentację big-endian (''host to network''), natomiast kolejne dwie dokonują konwersji w drugą stronę (''network to host'').
Można również skorzystać z pliku nagłówkowego endian.h, w którym definiowane są makra pozwalające określić porządek bajtów:
 
<source lang="c">
#include <endian.h>
#include <stdio.h>
return 0;
}
</source>
 
Na podstawie makra __BYTE_ORDER można skonstruować funkcję, która będzie konwertować liczby pomiędzy porządkiem różnymi porządkami:
 
<source lang="c">
#include <endian.h>
#include <stdio.h>
return 0;
}
</source>
 
Ciągle jednak polegamy na niestandardowym pliku nagłówkowym endian.h. Można go wyeliminować sprawdzając porządek bajtów w czasie wykonywania programu:
 
<source lang="c">
#include <stdio.h>
#include <stdint.h>
}
return 0;
}
</source>
 
Powyższe przykłady opisują jedynie część problemów jakie mogą wynikać z próby przenoszenia binarnych danych pomiędzy wieloma platformami. Wszystkie co więcej zakładają, że bajt ma 8 bitów, co wcale nie musi być prawdą dla konkretnej architektury, na którą piszemy aplikację. Co więcej liczby mogą posiadać w swojej reprezentacje bity wypełnienia (ang. ''padding bits''), które nie biorą udziały w przechowywaniu wartości liczby. Te wszystkie różnice mogą dodatkowo skomplikować kod. Toteż należy być świadomym, iż przenosząc dane binarnie musimy uważać na różne reprezentacje liczb.
Przy zwiększaniu przenośności kodu może pomóc preprocessor. Przyjmijmy np., że chcemy korzystać ze słówka kluczowego inline wprowadzonego w standardzie C99, ale równocześnie chcemy, aby nasz program był rozumiany przez kompilatory ANSI C. Wówczas, możemy skorzystać z następującego kodu:
 
<source lang="c">
#ifndef __inline__
# if __STDC_VERSION__ >= 199901L
# endif
#endif
</source>
 
a w kodzie programu zamiast słówka inline stosować __inline__. Co więcej, kompilator GCC rozumie słówka kluczowe tak tworzone i w jego przypadku warto nie redefiniować ich wartości:
 
<source lang="c">
#ifndef __GNUC__
# ifndef __inline__
# endif
#endif
</source>
 
Korzystając z kompilacji warunkowej można także korzystać z różnego kodu zależnie od (np.) systemu operacyjnego. Przykładowo, przed kompilacją na konkretnej platformie tworzymy odpowiedni plik config.h, który następnie dołączamy do wszystkich plików źródłowych, w których podejmujemy decyzje na podstawie zdefiniowanych makr. Dla przykładu, plik config.h:
 
<source lang="c">
#ifndef CONFIG_H
#define CONFIG_H
#endif
</source>
 
Jakiś plik źródłowy:
 
<source lang="c">
#include "config.h"
rob_cos_wersja_dla_linux();
#endif
</source>
 
Istnieją różne narzędzia, które pozwalają na automatyczne tworzenie takich plików config.h, dzięki czemu użytkownik przed skompilowaniem programu nie musi się trudzić i edytować ich ręcznie, a jedynie uruchomić odpowiednie polecenie. Przykładem jest zestaw autoconf i automake.
9

edycji