So, nun ist der Compiler einsatzbereit
und wir können testweise eine Konsolen-Anwendung erstellen. Eine
Konsolen-Anwendung ist eine Windows-Anwendung ohne Fenster, welche in
der Eingabeauffordung läuft und optisch wie eine "normale"
MS-DOS-Anwendung aussieht. Bevor hier wieder zum x-ten Mal das "Hello
World!"-Beispiel wiederholt wird, möchten wir lieber was anderes
machen. Der Compiler beherrscht auch (nicht standardisierte) __int64
und double long-Variablen. Der Wertebereich von __int64 reicht von
-2^63 bis +2^63-1.
Anbei ein Beispiel: Wir berechnen wieviele Meter ein Lichtstrahl in ca.
1000 Jahren zurücklegt und stoßen dabei an die Grenze von
2^63:
INT64.C:
#include <stdio.h>
#include <math.h>
#define LIGHTSPEED
299792458 // Lichtgeschwindigkeit in Meter pro
Sekunde
int main(void)
{
__int64 iEntf,iLj;
long double
dEntf,dLj;
int i,j;
i=sizeof(dEntf);
j=sizeof(iEntf);
printf("\n Speicherbedarf:
long double %d Bytes - __int64 %d
Bytes",i,j);
iEntf=1;
for (i=0; i<62; i++)
iEntf=iEntf*2;
iEntf--;
iEntf=2*iEntf+1; //
2 hoch 63 - 1
#ifdef __BORLANDC__
printf("\n\n Maximalwert
__int64: %Ld ",iEntf);
#else
printf("\n\n Maximalwert
__int64: %lld ",iEntf);
#endif
printf("\n\n Entfernung,
die ein Lichtstrahl zuruecklegt: ");
printf("\n\n
__int64
long double ");
iLj=LIGHTSPEED;
iLj=iLj*60*60*24;
// "Lichttag"
dLj=(long double)
LIGHTSPEED *60.0*60.0*24.0;
iLj=iLj*365 +
iLj/4; // "Lichtjahr"
dLj=dLj*365.0 + dLj/4.0;
for (i=970; i<980; i++)
{
iEntf=iLj*i;
dEntf=dLj*i;
printf("\n %4d
Lichtjahre = ",i);
#ifdef __BORLANDC__
printf("%20Ld",iEntf);
printf(" =
%23.1Lf Meter ", dEntf);
#else
printf("%20lld",iEntf);
printf(" =
%23.1llf Meter ", dEntf);
#endif
}
printf("\n");
return(0);
}
Wichtig ist der doppelte Unterstrich vor und nach __BORLANDC__ ; so
kann man das Programm zum Vergleich auch unter Microsoft Visual Studio
ab Version 2005 compilieren. Wie man sieht, sind die
Formatierungsparameter unter printf bei beiden Compileren
unterschiedlich.
__BORLANDC__ enthält übrigens den Wert 1361 (dezimal), was
551 hexadezimal entspricht -> der Versionsnummer des Compilers.
Compiliert wird mit "BCC32 INT64.C" (auf der Eingabeaufforderung
eingeben).
Das Ergebnis ist eine Datei namens "INT64.EXE" mit einer
Dateigröße von 52.736 Bytes.
Startet man das Programm, dann ergibt sich folgende Ausgabe:
Speicherbedarf: long double
10 Bytes - __int64 8 Bytes
Maximalwert __int64:
9223372036854775807
Entfernung, die ein
Lichtstrahl zuruecklegt:
__int64
long double
970 Lichtjahre =
9176908558403376000 =
9176908558403376000.0 Meter
971 Lichtjahre =
9186369288875956800 =
9186369288875956800.0 Meter
972 Lichtjahre =
9195830019348537600 =
9195830019348537600.0 Meter
973 Lichtjahre =
9205290749821118400 =
9205290749821118400.0 Meter
974 Lichtjahre =
9214751480293699200 =
9214751480293699200.0 Meter
975 Lichtjahre =
-9222531862943271616 =
9224212210766280000.0 Meter
976 Lichtjahre =
-9213071132470690816 =
9233672941238860800.0 Meter
977 Lichtjahre =
-9203610401998110016 =
9243133671711441600.0 Meter
978 Lichtjahre =
-9194149671525529216 =
9252594402184022400.0 Meter
979 Lichtjahre =
-9184688941052948416 =
9262055132656603200.0 Meter
Man sieht deutlich den Überlauf der __int64-Variable, während
die long double-Variable noch funktioniert. Übrigens: Unter
Microsoft Visual Studio belegt long double auch nur 8 Bytes und
entspricht somit einer "normalen" double-Variable und liefert daher bei
dieser Größenordnung nicht so genaue Werte.
Info: Für __int64 kann man auch LONGLONG verwenden (#include
<windows.h> erforderlich). Man kann die Ausgabe von __int64 (bzw.
LONGLONG) auch mit %lld anstatt %Ld formatieren. Long double
benötigt aber unbedingt %Lf, %llf würde unsinnige Werte
ausgeben.
Der Compiler beherrscht in der Konsole auch die von der DOS-Version
gewohnten Befehle für bunte und am Bildschirm positionierte Texte
wie clrscr, textcolor, textbackground, cprintf, gotoxy, wherex, wherey
ohne Verrenkungen (#include <conio.h> erforderlich). Dann
läuft allerdings das Programm nicht mehr mit wine unter Linux.
Bei dieser Gelegenheit noch eine Warnung zur Vorsicht bei
Integer-Divisionen:
#include <stdio.h>
int main(void)
{
int i;
double c,f,f1,f2,f3;
for (i=0; i<=100; i+=10)
{
c=i;
f=9.0/5.0 * c
+ 32.0; // perfekt, alle
ganzzahligen Double-Werte richtig "markiert"
f1=9/5 * c +
32; // das ist problematisch weil
int 9
/ int 5 = int 1!
f2=9.0/5 * c +
32; // bei einem Double-Wert und einem
Integer-Wert stimmt auch das
Ergebnis
f3=9/5.0 * c +
32;
printf("\n
%5.1f Celsius = %6.2f Fahrenheit %6.2f
%6.2f %6.2f ",c,f,f1,f2,f3);
}
printf("\n");
return(0);
}
Ergebnis:
0.0 Celsius =
32.00 Fahrenheit 32.00
32.00 32.00
10.0 Celsius = 50.00
Fahrenheit 42.00
50.00 50.00
20.0 Celsius = 68.00
Fahrenheit 52.00
68.00 68.00
30.0 Celsius = 86.00
Fahrenheit 62.00
86.00 86.00
40.0 Celsius = 104.00
Fahrenheit 72.00 104.00 104.00
50.0 Celsius = 122.00
Fahrenheit 82.00 122.00 122.00
60.0 Celsius = 140.00
Fahrenheit 92.00 140.00 140.00
70.0 Celsius = 158.00
Fahrenheit 102.00 158.00 158.00
80.0 Celsius = 176.00
Fahrenheit 112.00 176.00 176.00
90.0 Celsius = 194.00
Fahrenheit 122.00 194.00 194.00
100.0 Celsius = 212.00
Fahrenheit 132.00 212.00 212.00
Wie man sieht, bei einer Division von zwei Integer-Werten kommt wieder
ein Integerwert raus und da werden wie im Fall 9 / 5 beinhart alle
Dezimalstellen abgeschnitten! (Falsches Ergebnis 1!) Daher
sicherheitshalber alle ganzzahligen Doublewerte mit
abschließendem .0 schreiben, also 9.0 / 5.0 (ergibt 1.8).
Und zum Abschluß noch ein Hexdump-Utility:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
void adresse(long);
long powlong(long, long);
int main(int argc, char **argv)
{
char
ch[260];
/* MAX_PATH = 260 */
unsigned char c,c1,c2;
int i,j,s,m;
int
max=16;
/* Vorsicht: max mind. 2 kleiner als Stringlaenge von ch[]! */
int
sw=0;
/* sw=0 ANSI-Zeichensatz (MS-DOS); sw!=0 OEM-Zeichensatz (Windows) */
long l=0;
FILE *dz;
if (argc<2)
{
printf("\n
Hexdump Utility");
printf("\n\n
Usage: %s Filename columns
[/oem]",argv[0]);
printf("\n\n
colums = number of columns in a row;
from 1 to 128");
printf("\n\n
colums argument not necessary; in this
case 16 columns are used");
printf("\n\n
/oem or /win = use OEM char set
[Windows]");
printf("\n
(standard is ANSI char set [DOS or
Console])");
printf("\n");
exit(1);
}
if
((dz=fopen(argv[1],"rb"))==NULL)
{
printf("\n
Cannot open file \"%s\" - possibly file
not found!",argv[1]);
printf("\n");
exit(2);
}
if (argc>2)
{
for (i=2;
i<argc; i++)
{
m=atoi(argv[i]);
if
(m)
{
if (m<1) m=1;
if (m>128) m=128;
max=m;
/* Vorsicht: max mind. 2 kleiner als Stringlaenge von ch[]! */
}
else
{
strcpy(ch, argv[i]);
for (j=0; ch[j]; j++)
ch[j]=(char)
tolower(ch[j]);
if (strstr(ch, "/oem") ||
strstr(ch, "\\oem") ||
strstr(ch, "/win") || strstr(ch, "\\win")) sw=1;
}
}
}
s=0;
for (i=0; i<max+2; i++)
ch[i]='\0';
while
(fread(&c,1,1,dz)>0)
{
if (c>31
&& c<128)
ch[s]=c;
else
{
ch[s]='.';
if
(c==0x84 || c==0xE4) ch[s] = (!sw) ?
0x84 : 0xE4; /* ä */
if
(c==0x94 || c==0xF6) ch[s] = (!sw) ?
0x94 : 0xF6; /* ö */
if
(c==0x81 || c==0xFC) ch[s] = (!sw) ?
0x81 : 0xFC; /* ü */
if
(c==0x99 || c==0xD6) ch[s] = (!sw) ?
0x99 : 0xD6; /* Ä */
if
(c==0x8E || c==0xC4) ch[s] = (!sw) ?
0x8E : 0xC4; /* Ö */
if
(c==0x9A || c==0xDC) ch[s] = (!sw) ?
0x9A : 0xDC; /* Ü */
if
(c==0xE1 || c==0xDF) ch[s] = (!sw) ?
0xE1 : 0XDF; /* ß */
}
c1 = c/16 + 48;
if (c1>57)
c1+=7;
c2 = c%16 + 48;
if (c2>57)
c2+=7;
if (s==0)
adresse(l); /* oder einfach nur if (!s) */
printf("%c%c
",c1,c2);
if (++s==max)
{s=0; printf(" *%s*\n",ch);}
if
(++l>=16777216) l=0;
/* 256*256*256 */
}
if (s!=0) /* oder
einfach nur if (s)
{
j=max-s;
for
(i=0; i<j; i++)
{
printf(" ");
ch[max-i-1]='*';
}
printf(" *%s*\n",ch);
}
fclose(dz);
return(0);
}
/* nur für positiven
Exponenten */
/* erg = bas hoch
exp */
long powlong(long bas, long exp)
{
long i,erg=bas;
if (exp==0) return(1);
if (exp<0 || bas==0)
return(0);
for (i=1; i<exp; i++)
erg*=bas;
return(erg);
}
void adresse(long a)
{
long p;
int i,j=0;
char he[7];
unsigned char c;
for (i=5; i>=0; i--)
{
p =
powlong(16,i);
c = a/p + 48;
a = a%p;
if (c>57)
c+=7;
he[j++]=(char)
c;
}
he[j]='\0';
printf("%s
",he);
}
Ausgabe-Beispiel: (hier die compilierte EXE-Datei des Dump-Programmes)
000000 4D 5A 50 00 02
00 00 00 04 00 0F 00 FF FF 00
00 *MZP.............*
000010 B8 00 00 00 00
00 00 00 40 00 1A 00 00 00 00
00 *........@.......*
000020 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 *................*
000030 00 00 00 00 00
00 00 00 00 00 00 00 00 02 00
00 *................*
000040 BA 10 00 0E 1F
B4 09 CD 21 B8 01 4C CD 21 90
90 *........!..L.!..*
000050 54 68 69 73 20
70 72 6F 67 72 61 6D 20 6D 75
73 *This program mus*
000060 74 20 62 65 20
72 75 6E 20 75 6E 64 65 72 20
57 *t be run under W*
000070 69 6E 33 32 0D
0A 24 37 00 00 00 00 00 00 00
00 *in32..$7........*
Man könnte anstatt der long-Variablen auch int-Variablen
verwenden; beide sind unter Win32 4 Bytes groß. Aber mit long
anstatt int kann das Programm auch unter MS-DOS compiliert werden.
Noch ein Tipp: Wenn man schon Lust verspürt, so eine
Windows-Konsolen-Anwendung auch unter "echtem" DOS laufen zu lassen,
dann empfiehlt sich der HX-DOS-Extender (siehe
Link in
Wikipedia).
Umlautproblematik bei Konsolenanwendungen
Leider ist es so, dass in der Konsole (und auch bei MS-DOS-Anwendungen)
ein anderer Zeichensatz für die Umlaute verwendet wird, als unter
Windows. Schreibt man daher
printf("äöüÄÖÜß") mit einem
Windows-Editor und compiliert das Ganze dann als Konsolenanwendung,
dann erscheinen ganz andere Zeichen auf dem Bildschirm. Bei der
Erstellung
von "richtigen" Windows-Anwendungen tritt dieser Effekt nicht mehr auf,
da dann sowohl der Editor als auch die fertige Anwendung den gleichen
Zeichensatz verwenden.
Das Problem lässt sich aber lösen:
- den Source-Code mit einem MS-DOS-basierten Editor (z.B. EDIT.COM,
welcher unter Windows XP immer noch dabei ist) öffnen und die
Umlaute manuell umbessern. Unter 64Bit-Betriebssystemen laufen aber
keine 16Bit-DOS-Anwendungen mehr. Hier hilft der Emulator "DOSBox"
weiter (siehe
Link
in Wikipedia).
- das Zeichen durch den entsprechenden HEX-Code ersetzen:
z.B. Ä durch \x8E ersetzen. Also printf("\x8Epfel und
Birnen") -> Ausgabe "Äpfel und Birnen"
Zeichen
|
Konsole
|
Windows
|
ä
|
84
|
E4
|
ö
|
94
|
F6
|
ü
|
81
|
FC
|
Ä
|
8E
|
C4
|
Ö
|
99
|
D6
|
Ü
|
9A
|
DC
|
ß
|
E1
|
DF
|
Alle Zeichencodes in Hex!