C-Programmierung: Float in Int umwandeln Im Unterforum Off-Topic - Beschreibung: Alles andere was nirgendwo reinpasst
Autor |
C-Programmierung: Float in Int umwandeln |
|
|
|
|
BID = 628520
bastler16 Schreibmaschine
Beiträge: 2140 Wohnort: Frankreich
|
|
Hallo Forum,
mal eine Programmierfrage für DonComi und Co.
OS=Windows XP, Sprache=C, IDE=Dev-C++ 4.9.9.2.
Ich (Anfänger!) möchte einen float in einen int umwandeln und dabei auf die nächstkleinere Ganzzahl abrunden, d.h. die Kommastellen einfach abschneiden. Aus 3 oder 3,4 oder 3,9 wird also immer 3.
Aktuell löse ich das mit einem Cast, scheint auch zu funktionieren.
float f;
int i;
//Irgendwas sinnvolles in f packen...
i=(int)f;
1. Ist dieses Verhalten (Abrunden) irgendwo standardisiert (ANSI-C?) und meine Methode in Ordnung oder ist das totaler Murks?
2. Müsste der Compiler nicht eine Warnung ausgeben wenn ich den Cast weglasse?
Schönen Abend (ist's in DE auch so warm?) |
|
BID = 628525
perl Ehrenmitglied
Beiträge: 11110,1 Wohnort: Rheinbach
|
|
Überleg dir auch, was mit negativen Zahlen passieren soll.
Wenn da auch die nächst kleinere rauskommen soll, d.h. -3,3 wird nach -4 abgerundet, solltest du floor verwenden.
P.S.:
Zitat :
| ist's in DE auch so warm? | Ja, das Autothermometer zeigte um 20:00 noch 32°C.
Vor ein paar Minuten gab es aber ein paar kräftige Gewitterböen (ohne Blitze), die für Abkühlung sorgten. Momentan noch 21°C draussen.
[ Diese Nachricht wurde geändert von: perl am 20 Aug 2009 22:07 ] |
|
BID = 628535
bastler16 Schreibmaschine
Beiträge: 2140 Wohnort: Frankreich
|
Danke für den Hinweis perl.
Die negativen Zahlen sind im konkreten Fall unproblematisch, es geht um Noten und die können nicht negativ sein (Hätte ich mal sagen sollen, die Hitze hier verhindert effektiv das Denken ).
Kurz ein paar Hintergrundinfos: Der (das?) float ist Ergebnis einer Berechnung und soll in eine Note (1-6) konvertiert werden. Dazu muss wie beschrieben abgerundet werden, da die Tabelle Punkte<-->Note nur für Ganzzahlen existiert.
Die Konvertierung erfolgt in einer Extrafunktion ganz anfängermäßig mit switch...case, besagte Funktion ist als char* func(int ) definiert. Char* deswegen, weil man z.B. "2+" nicht als Zahl übergeben kann.
Zum floor: Laut http://www.codecogs.com/reference/c/math.h/32.php liefert floor einen double (bzw. long oder float, je nachdem welche "Version" man benutzt) zurück. Ich brauche aber einen Int, also müsste ich doch wieder casten, oder? Ist das eine bessere Lösung als ein "direkter" Cast (s.o.)?
//....
#include <math.h>
float f;
int i;
//tu was sinnvolles
i=( int)floor(f);
Gibt es eigentlich unsigned float?
edit Ergänzung (Ist wirklich zu warm...)
[ Diese Nachricht wurde geändert von: bastler16 am 20 Aug 2009 22:35 ]
|
BID = 628537
perl Ehrenmitglied
Beiträge: 11110,1 Wohnort: Rheinbach
|
Zitat :
| Gibt es eigentlich unsigned float? |
Meines Wissens nicht.
Die Gleitkommazahlen brauchen für Exponent und Mantisse ohnehin soviele Bits, dass das eine Vorzeichenbit nicht mehr wehtut.
|
BID = 628734
DonComi Inventar
Beiträge: 8605 Wohnort: Amerika
|
Zitat :
|
Müsste der Compiler nicht eine Warnung ausgeben wenn ich den Cast weglasse?
|
Dies wäre eine implizierte Typumwandlung (Casting).
Da wird kein explizites Typcasting verlangt, der Compiler schweigt also.
Habs mal ausprobiert, selbst bei pedantischem Umgehen mit dem Quellkode meckert der gcc nicht.
Fehler erzeugt er aber z.B., wenn ich einen Zeiger in eine Gleitkommazahl per Cast umwandeln will (warum auch immer... ). Selbst das ginge aber (rein theoretisch).
Nur muss man eben bedenken, dass von Gleitkomma- zu Ganzzahl fast immer ein Informationsverlust auftritt (logisch).
Zitat :
|
besagte Funktion ist als char* func(int ) definiert.
|
Soso.
Und auf welchen Speicherbereich zeigt der Zeiger, der zurückgegeben wird? Wenn er auf eine lokale Variable/Array/Speicherbereich (wie auch immer) zeigt, und die einen Zeiger darauf zurückgibst, dann sieht es schlecht aus.
Anders wäre eine Zeigerrückgabe auf z.B. statischen Speicher, etwa so:
char * func ( void )
{
static char speicher[2] ;
/* mach was */
return &speicher[0];
}
Besser, weil man den String dann gleich ausgeben könnte wäre:
...
static char speicher[3] = {0,0,0};
oder
static char speicher[3] = "\0\0\0";
Denn dann wäre es ein korrekter C-String, also nullterminiert.
Zitat :
|
Char* deswegen, weil man z.B. "2+" nicht als Zahl übergeben kann.
|
Man kann das schon, z.B. in dem man sich eigene Datentypen baut, oder aber Aufzählungen verwendet, das sind reine Zahlen.
Beispiel:
typedef enum {
_1PLUS,
_1
_1MINUS,
_2PLUS,
_2,
_2MINUS,
} NOTEN;
Oder so. Man erkennt sofort, dass hier noch keine Notwendigkeit besteht, solche Dinge zu nutzen. Woanders ergeben sie viel Sinn, da sie u.U. wesentlich schneller sind.
Aber mit dem String ist das gut so
Vor allem, weil man direkt ausgebbare Noten hat, ohne wiederum z.B. Tabellen für Klartext zu benötigen.
_________________
|
BID = 628770
bastler16 Schreibmaschine
Beiträge: 2140 Wohnort: Frankreich
|
Hallo Don,
Danke für den kleinen Programmierkurs
Zitat :
| Dies wäre eine implizierte Typumwandlung (Casting).
Da wird kein explizites Typcasting verlangt, der Compiler schweigt also.
Habs mal ausprobiert, selbst bei pedantischem Umgehen mit dem Quellkode meckert der gcc nicht. |
Warum wird hier kein Cast verlangt?
Ich werd mal gucken ob ich den Compiler (laut Wiki die Windowsversion von GCC) so oft wie möglich zum meckern bringen kann, desto sauberer programmiert man.
Zitat :
| Nur muss man eben bedenken, dass von Gleitkomma- zu Ganzzahl fast immer ein Informationsverlust auftritt (logisch). |
In meinen Fall ist das ja gewollt.
Bevor wir komplett abschweifen(*): Kann ich die Umwandelung und Abrundung per Cast so lassen oder muss ich das ändern?
(*) Find ich wunderbar(will ja was lernen), nur fehlt mir noch die Antwort auf meine eigentliche Frage.
Zitat :
| Soso.
Und auf welchen Speicherbereich zeigt der Zeiger, der zurückgegeben wird? Wenn er auf eine lokale Variable/Array/Speicherbereich (wie auch immer) zeigt, und die einen Zeiger darauf zurückgibst, dann sieht es schlecht aus. |
Nun ja , hier trifft Anfänger auf Profi.
Codeausschnitt (sämtliche Namen sind gekürzt):
Code : |
int main(void)
{
float note;
//tu
//was
//sinnvolles
printf("Punkte: %.1f, entpricht Note %s\n\n",note,konvertiere(note));
//...
}
char* konvertiere(const float p)
{
int i=(int)p; //Nachkommastellen abschneiden
//IST DAS EINE GUTE METHODE?
switch(i)
{
case 15: return "1+";
case 14: return "1";
case 13: return "1-";
case 12: return "2+";
//und so weiter
default: fehler("Ungültiger Wert konvertiere");
}
}
void fehler (const char* f)
{
system("CLS");
printf("Fehler: %s\n\n",f);
system("PAUSE");
exit(EXIT_FAILURE);
}
|
|
Anmerkung: Die Sonderzeichen (z.B. 'ü' in "ungültiger Wert") sind durch \... (ü=201) ersetzt, das verhunzt die Forensoftware aber (immer noch).
Ich wollte es bequem haben und hab einfach gedacht ("Sie sollen aber nicht denken.") der Compiler wird es schon richten...
So, jetzt kannst du mein Programm und mich zerlegen...
Zitat :
|
Anders wäre eine Zeigerrückgabe auf z.B. statischen Speicher, etwa so:
//...
|
Was ist statischer Speicher?
Ich rate mal: Der Compiler (oder was auch immer) reserviert den Speicher für die Variablen nur solange die Funktion ausgeführt wird, danach "verfällt" er. Statischer Speicher bleibt bis zum Ende des Programmes erhalten.
Richtig?
Danke für die Antwort (und die nächsten gleich mit ), von Leuten wie dir kann man eine Menge lernen. Von enum z.B. hatte ich z.B. noch nie gehört.
[ Diese Nachricht wurde geändert von: bastler16 am 22 Aug 2009 11:05 ]
|
BID = 628825
DonComi Inventar
Beiträge: 8605 Wohnort: Amerika
|
Hallo Bastler,
Zitat :
|
Warum wird hier kein Cast verlangt?
[...]
Kann ich die Umwandelung und Abrundung per Cast so lassen oder muss ich das ändern?
|
Du kannst das so lassen, das funktioniert.
Er wird aber immer abrunden. Das bedeutet, dass z.B. sowohl 1.49f als auch 1.99999f zu 1 abgerundet werden. Genaugenommen schnibbelt er bloß den Nachkommateil ab.
Es ist auch korrekt, dass keine Fehler- bzw. Warnungsmeldung kommt. Das sind gewollte und völlig zulässige implizite Typumwandlungen. Da wird nicht drauf hingewiesen, der Compiler nimmt an, dass diese Umwandlung gewünscht ist.
Guter Stil zeichnet sich aber dadurch aus, dass man gerade in diesen Situationen dennoch einen (symbolischen) cast-Operator verwendet. Damit drückt man nur noch aus, dass man sich vollstens bewusst ist, dass ein Datentyp umgewandelt wird.
Also lass den Cast drin .
Zitat :
|
char* konvertiere(const float p)
|
Auch völlig in Ordnung, aber schöner ist es, wenn man das const nach dem eigentlichen Datentypen schreibt:
(float const p)
Zitat :
|
Ich werd mal gucken ob ich den Compiler (laut Wiki die Windowsversion von GCC) so oft wie möglich zum meckern bringen kann, desto sauberer programmiert man.
|
Der MinGW?
Dann solltest du noch, wenn du wirklich 100% ansikonform sein willst, die Parameter
-Wall (für Warnungen) -pedantic (pedantischer Umgang mit Kode) und -std=c89 / -ansi
anhängen. -Wall und -pedantic haben ansich mit ANSI nichts zu tun, erhöhen aber den Output über Unfeinheiten im Kode. Aber auch da wird nicht gemeckert bei einem impliziten Cast!
Zitat :
|
Was ist statischer Speicher?
Ich rate mal: Der Compiler (oder was auch immer) reserviert den Speicher für die Variablen nur solange die Funktion ausgeführt wird, danach "verfällt" er. Statischer Speicher bleibt bis zum Ende des Programmes erhalten.
|
Es gibt diverse Speicherklassen.
Eine ist static.
Das bedeutet zweierlei: Erstens sind "globale" Variablen im gesamten Quellkode (der jeweiligen Quelltextdatei!) verfügbar.
Im Falle einer Deklaration in einer Funktion z.B. sind es lokale Variablen, die nur innerhalb dieser Funktion genutzt werden können (scope-Bereich). Allerdings liegen sie auch in einem anderen Segment (das könnte bei AVRs z.B. der Heap sein, bei größeren bekommen sie u.U. ein eigenes Segment) und verlieren ihren Wert nicht, wie das bei der Speicherklasse auto wäre. Denn diese landen i.d.R. auf dem Stack.
Edit: aber dein Raten stimmt schon. Er verfällt regelrecht bei Variablen der Speicherklasse auto, da diese ja auf dem Stack liegen. Der Stackpointer aber ändert sich beim Verlassen der Funktion. Das heißt nicht zwingend, dass der Wert dabei verloren geht (er steht ja noch dort, wo eben noch Stack war), aber er könnte gleich wieder überschrieben werden, wenn der Stack wieder wächst. Im Gegenteil dazu sind die statischen Variablen so lange vorhanden (und deren Referenz gültig) wie das Programm lebt .
Die Speicherklasse auto ist übrigens auch implizit.
Wenn du
int a;
schreibst, meckert der Compiler nicht, auch wenn keine Speicherklasse angegeben wurde. Implizit nimmt er daher folgendes an:
auto int a;
Na, alles klar ?
_________________
[ Diese Nachricht wurde geändert von: DonComi am 22 Aug 2009 16:42 ]
|
BID = 629033
bastler16 Schreibmaschine
Beiträge: 2140 Wohnort: Frankreich
|
Hallo Don,
Zitat :
| Du kannst das so lassen, das funktioniert. |
Direkte und klare Antwort, Wunderbar.
Zitat :
| Er wird aber immer abrunden. Das bedeutet, dass z.B. sowohl 1.49f als auch 1.99999f zu 1 abgerundet werden. Genaugenommen schnibbelt er bloß den Nachkommateil ab. |
Genau das soll er ja auch...
Zitat :
| Auch völlig in Ordnung, aber schöner ist es, wenn man das const nach dem eigentlichen Datentypen schreibt:
(float const p)
|
Achso? Wenn das so üblich ist, ich werde es mir merken.
Zitat :
| Der MinGW?
Dann solltest du noch, wenn du wirklich 100% ansikonform sein willst, die Parameter
-Wall (für Warnungen) -pedantic (pedantischer Umgang mit Kode) und -std=c89 / -ansi
|
Nun ja...
Alleine der Parameter -ansi erzeugt jede Menge Warnungen und Fehler (lässt sich nicht kopieren, s. Bild). Bevor ich mich damit rumschlage, welche Parameter sind den sinnvoll? Ob meine Programme ansikonform sind ist mir eigentlich ziemlich egal, ich will mir nur von Anfang an einen "guten Programmierstil" angewöhnen...
Zitat :
| Es gibt diverse Speicherklassen. [.......] Na, alles klar ? |
Oula
Nachdem ich Wikipedia und Google bemüht habe, mal sehen ob ich das ganze verstanden hab...
Variablen die (innerhalb einer Funktion) als auto definiert sind werden bei jedem Aufruf der entsprechenden Funktion "angelegt" (es wird Speicher zugewiesen) und ggf. initialisiert. Beim Beenden der Funktion wird der Speicher wieder freigegeben und kann von anderen Funktionen/Programmen überschrieben werden.
Wenn man die Variable als static definiert wird sie nur beim 1. Aufruf der Funktion angelegt und bleibt dann bis zum Programmende erhalten.
Globale Variablen werden standardmäßig als static deklariert, lokale als auto.
Stimmt das bis hier hin?
Zitat :
| Es gibt diverse Speicherklassen.
Im Falle einer Deklaration in einer Funktion z.B. sind es lokale Variablen, die nur innerhalb dieser Funktion genutzt werden können (scope-Bereich). Allerdings liegen sie auch in einem anderen Segment (das könnte bei AVRs z.B. der Heap sein, bei größeren bekommen sie u.U. ein eigenes Segment) und verlieren ihren Wert nicht, wie das bei der Speicherklasse auto wäre. Denn diese landen i.d.R. auf dem Stack. |
Was ist dieser scope-Bereich? Ist ein Segment ein "Stück Speicher"? Was den "Heap" angeht: Wikipedia meint http://de.wikipedia.org/wiki/Dynamischer_Speicher
Wenn ich das richtig verstehe (am Beispiel vom AVR, den kenne ich besser als einen PC): Static-Variablen werden irgendwo im SRAM abgelegt und bleiben dort, für auto-Variablen wird der Stack genutzt. Da aber alle Variablen innerhalb einer Funktion standardmäßig auto sind müsste doch ständig der Stackpointer verändert werden, oder?
Irgendwie blicke ich da nicht durch...
Wie ist das denn mit meiner Funktion konvertiere?
Als Erinnerung:
Code : |
char* konvertiere(const float p)
{
int i=(int)p; //Nachkommastellen abschneiden
//IST DAS EINE GUTE METHODE?
switch(i)
{
case 15: return "1+";
case 14: return "1";
case 13: return "1-";
case 12: return "2+";
//und so weiter
default: fehler("Ungültiger Wert konvertiere");
}
}
|
|
Ich lege ja hier keine Variable für den Rückgabewert an, sondern gebe direkt "einen Wert" zurück. Wird da einfach Speicher zugewiesen, der Text gespeichert und der Pointer zurückgegeben? Wenn das ganze als auto passiert ist das doch Murks, weil der Pointer potentiell schon wieder etwas anderes "enthält" (abus de language, ich meine natürlich er zeigt auf eine Speicherstelle welche potentiell etwas anderes enthält).
Danke!
edit: Bild vergessen
edit: erweitert nach Lesen in der Wikipedia
[ Diese Nachricht wurde geändert von: bastler16 am 23 Aug 2009 15:27 ]
[ Diese Nachricht wurde geändert von: bastler16 am 23 Aug 2009 15:32 ]
|
BID = 629043
DonComi Inventar
Beiträge: 8605 Wohnort: Amerika
|
Tach Bastler,
Zu den Fehler- und Warnungsmeldungen kann ich nicht viel sagen, da ich ja keinen Quelltext habe.
Ich persönlich programmiere nur mit einem einfachen Editor (der gerade mal Einrückung und Schlüsselworthervorhebung ("syntax highlighting") unterstützt) und parallel dazu einer Shell sowie dem Programm make.
Die Ausgaben der verschiedenen Instanzen (Compiler, Linker, Assembler, etc) kann man dann auch mal gezielt in eine Datei umleiten und hier posten.
Sauber C-Programmieren lernt man nicht von Anbeginn. Das ist ein stetiger Prozess.
Eine finden Folgendes sauber:
int
calc_chk( int const a, int const b ) {
return a + b ;
}
Andere wiederum finden das grausam und schreiben es so (und meinen, es sei besserer Stil )
int
calc_chk(const int a, const int b)
{return a+b; }
andere wiederum favorisieren
int calc_chk (int const a, int const b)
{
}
und wiederum andere schreiben lieber
int calc_chk(int a, int b) {return a+b; }
Das sind alles Feinheiten, die weder gut noch schlecht sind; wichtiger ist, dass man sich einen Stil angewöhnt und diesen konsequent durchhält.
Andere wiederum meinen, sauberer Stil sei, den Quelltext möglichst einfach zu halten, während andere meinen, C voll auskosten zu müssen, indem sie auch z.B. den einzigen trinären Operator () ? () : () bis zum Gehtnichtmehr auskosten.
Andere wiederum machen sowas gerne (ich auch):
void machwas( void );
...
/* aufrufen */
(void) machwas ();
Ausreichen würde allerdings völlig
machwas();
Also, so langsam wird es klar: gerade bei C führen enorm viele Wege nach Rom, oft selbst die Irrwege.
Und wenn man lange mit dem gcc und der gnu-Erweiterung programmiert merkt man schnell, dass "dieses C" seine Vorteile hat, aber nicht mehr Ansikonform ist. Will man komplett plattformunabhängige Dinge in C kodieren, auch z.B. unter Linux für Windows, dann schaltet man die Erweiterungen ab und hat erstmal ein paar hundert Fehlermeldungen wegen Nichtkonformität.
Dank dem Präprozessor kann man diese aber meist wieder hinbiegen, denn z.B. gibt es normalerweise die Inline-Assembleranweisung in der Form asm garnicht.
In GNU-gcc wäre Folgendes in Ordnung:
asm volatile
(
"sub $0x8,%esp \n\t"
"mov 0x80494a0,%eax \n\t"
"test %eax,%eax \n\t"
"je 8048371 \n\t"
);
Während der Compilierversuch mit dem Schalter -ansi fehlschlagen würde, da dort das Schlüsselwort asm nicht exisitert, funktioniert es mit der GNU-Erweiterung hervorragend.
Um das ansikonform zu machen ist man aber nicht gezwungen, alles umzuschreiben, man nutzt schlicht den Präprozessor:
#define asm __asm__
Der Präprozessor macht eine reine Textersetzung (und Makroexpansion) und ersetzt alle "asm"s durch die ansikonforme Version "__asm__". Schon läuft die Laube .
Oha, lass dich nicht abschrecken . C ist eigentlich einfach, diesen ganzen Kram kannst du getrost vergessen, wenn du nur für eine Plattform entwickelst.
Aber interessant ist es schon, gerade wenn man sich die ersten C-Versioen anschaut und sieht, was C heute kann. Das war nicht immer so.
Und dann gibt es ja auch noch C++...
_________________
|
BID = 629045
DonComi Inventar
Beiträge: 8605 Wohnort: Amerika
|
Sorry, Überschneidung mit Edit
---
Zitat :
|
Variablen die (innerhalb einer Funktion) als auto definiert sind werden bei jedem Aufruf der entsprechenden Funktion "angelegt" (es wird Speicher zugewiesen) und ggf. initialisiert. Beim Beenden der Funktion wird der Speicher wieder freigegeben und kann von anderen Funktionen/Programmen überschrieben werden.
Wenn man die Variable als static definiert wird sie nur beim 1. Aufruf der Funktion angelegt und bleibt dann bis zum Programmende erhalten.
Globale Variablen werden standardmäßig als static deklariert, lokale als auto.
Stimmt das bis hier hin?
|
Ja, im Großen und Ganzen schon, aber
1. nicht alle Variablen, die deklariert werden, werden nach den Optimierungen auch genutzt! Wenn beispielsweise eine Variable deklariert, aber nicht genutzt wird, wird sie später garnicht erst verwendet (also kein Speicher auf dem Stack geholt).
2. Der Speicher wird garnicht so direkt reserviert, man nutzt schlicht den Stack und den Stackpointer, um den Variablen Platz zu geben. Aber auch hier gilt: "auto" heißt nicht automatisch auf dem Stack! Im Falle von AVRs z.B. werden meist direkt Register genommen, solange nicht z.B. ein Zeiger auf diese Variable genutzt werden soll (Registervariablen haben keine Adresse [rein theoretisch schon, da die Register im RAM gemappt sind, aber egal... ]).
3. Da aber meist, z.B. in PC-Programmen, der Stack genutzt wird, verfällt der Speicher nach Verlassen der Funktion einfach, er wird also rein natürlich freigegeben. Der Stackpointer wird einfach wieder erhöht und schon beim Aufrufen der nächsten Funktion können da ganz andere Daten landen. Und das wiederum ist auch ein Problem. Durch gezieltes Manipulieren des Stack kann man Rücksprungadressen einschleusen, zu denen beim Verlassen dann üblerweise gesprungen wird...
4. lokale, statische Var werden zu Beginn des Programms, nicht erst bei Eintritt in die Funktion angelegt. Sie werden im Zuge der Initialisierungen noch lange vor main() angelegt und auf definierte Werte gebracht. Schau dir mal das Disassembling eines einfaches Programmes an, was dort vor main alles abläuft .
5. Globale Variablen können nicht lokal sein. Wird sie ohne explizite Speicherklasse angegeben (int a;) dann ist sie auto int a; wobei der Compiler daraus aber eine statische macht... auto heißt halt nicht automatisch lokal, sondern halt automatisch. Aber grundsätzlich sind sie statisch, wobei auch hier wieder Ausnahmen bei const und volatile auftreten...
_________________
|
BID = 629048
DonComi Inventar
Beiträge: 8605 Wohnort: Amerika
|
Puh, da kommt man ja kaum mit...
---
Zitat :
|
Was ist dieser scope-Bereich? Ist ein Segment ein "Stück Speicher"? Was den "Heap" angeht: Wikipedia meint http://de.wikipedia.org/wiki/Dynamischer_Speicher [1]
Wenn ich das richtig verstehe (am Beispiel vom AVR, den kenne ich besser als einen PC): Static-Variablen werden irgendwo im SRAM abgelegt und bleiben dort, für auto-Variablen wird der Stack genutzt. Da aber alle Variablen innerhalb einer Funktion standardmäßig auto sind müsste doch ständig der Stackpointer verändert werden, oder? [2]
Irgendwie blicke ich da nicht durch...
|
zu 1:
Ein AVR hat keine Segmente. Segmente sind schon Stück Speicher, aber ein wenig anders. Schau dir mal die Segmentierung des Speichers für die x86-Architekturen an. Ein Segment kann z.B. 16kB groß sein. Ein Programm bekommt dan einige Segmente, z.B. für Daten (statische Variablen), für Konstanten (schreibgeschützer Bereich im Speicher), für Programmkode (program segment) etc. Braucht man bei AVR nicht.
Der Heap ist Freispeicher. Da können, müssen aber keine statischen Daten drinliegen. Nehmen wir den AVR, ist einfacher: am RAMENDe beginnt der Stack, er wächst immer weiter Richtung RAMSTART. Er wächst also negativ. Auf dem anderen Ende des RAMs ist der Heap (im sozusagen gleichen Segment, da der AVR nur eines hat, wenn man so will. Die Grenze zwischen Stack und Heap ist also variabel, und kann ungünstigerweise auch kollidieren, z.B. wenn man zuviele rekursive Funktionen hat... )
Also, statische Daten werden ab RAMSTART angelegt und sind fest, während der Stack auf der anderen Seite ist.
zu 2. Es ist vielleicht noch nicht so klargeworden. die Speicherklasse auto meint, dass der Compiler die Speicherklasse selbst festlegt, nach eigenem Gutdünken. Er kann dafür sowohl direkt Register, als auch statische Speicherstellen oder Stackvariablen zuordnen.
Gerade im Zuge der Optimierungen werden Stackvariablen dann z.B. noch in Arbeitsregister verlegt, da das natürlich schneller geht und der AVRs auch mehrere davon hat (aber keine direkten Operationen im RAM ausführen kann).
Der Stackpointer wird ja auch ständig verändert! Beim Aufruf einer Funktion landet dort die Rücksprungadresse und der Pointer wird geändert. Bei jedem "Push" und "Pop" ändert er sich. Zudem kommt, dass größere Parameter/viele Parameter für Funktionen oft auch den Stack nutzen, um übergeben zu werden. Also der ist ständig am wachsen und schrumpfen .
_________________
|
BID = 629050
DonComi Inventar
Beiträge: 8605 Wohnort: Amerika
|
Code : |
switch(i)
{
case 15: return "1+";
case 14: return "1";
|
|
Das ist OK so, aber der Ordnung halber muss da noch eine break-Anweisung rein, auch wenn nach dem return Schluss ist:
Code : |
switch(i)
{
case 15: return "1+"; break;
case 14: return "1"; break;
|
|
---
Zitat :
|
Ich lege ja hier keine Variable für den Rückgabewert an, sondern gebe direkt "einen Wert" zurück. Wird da einfach Speicher zugewiesen, der Text gespeichert und der Pointer zurückgegeben? Wenn das ganze als auto passiert ist das doch Murks, weil der Pointer potentiell schon wieder etwas anderes "enthält" (abus de language, ich meine natürlich er zeigt auf eine Speicherstelle welche potentiell etwas anderes enthält).
|
Das ist alles implizit versteckt .
Du gibts einen Zeiger auf einen statischen konstanten Wert zurück.
Genaugenommen steht dort folgendes:
char const * const string1 = "1+\0";
char const * const string2 = "2+\0";
case x: return string1; break;
etc.
Das findet halt im Verborgenen statt. Das steht auch schon vor der Laufzeit alles fest . Wie gesagt, das auto hat nichts direkt mit dem Stack zu tun .
Wenn ich im Programm sowas habe wie:
char f[] = "Hallo Bastler!";
Dann muss ja diese Konstante auch im Programm vorhanden sein. Der Zeiger wird dann einfach nach f kopiert und gut ist . Intern werden dafür übrigens ganz interessante Symbole benutzt . Z.B. __LSring0001 oder so. Wenn du das weißt, kannst du die Zeiger auch darüber holen, aber keine Garantie, dass sie stimmen ...
Der macht das schon alles richtig .
_________________
[ Diese Nachricht wurde geändert von: DonComi am 23 Aug 2009 16:11 ]
|
BID = 629059
bastler16 Schreibmaschine
Beiträge: 2140 Wohnort: Frankreich
|
Hallo Don,
vielen Dank für die ausführlichen Erklärungen (Solltest Lehrer werden ),
ich hoffe/glaube... ich hab alles verstanden.
Zitat :
| Ich persönlich programmiere nur mit einem einfachen Editor (der gerade mal Einrückung und Schlüsselworthervorhebung ("syntax highlighting") unterstützt) und parallel dazu einer Shell sowie dem Programm make. |
Du verstehst ja auch was vonwegen Makefiles etc., für einen Anfänger ist eine IDE einfacher. Ich muss nur Strg+F11 bzw. F9 drücken und ab geht die Post.
Zitat :
|
Sauber C-Programmieren lernt man nicht von Anbeginn. Das ist ein stetiger Prozess.
|
Ja, das habe ich schon gemerkt.
Zitat :
| Andere wiederum meinen, sauberer Stil sei, den Quelltext möglichst einfach zu halten, während andere meinen, C voll auskosten zu müssen, indem sie auch z.B. den einzigen trinären Operator () ? () : () bis zum Gehtnichtmehr auskosten. |
Dieses ()?... ist schon ganz praktisch, z.B. um grammatikalisch korrekte Ausgaben zu erzeugen:
Nehmen wir mal an, ein Programm soll je nach Inhalt von anzahl_werte "Insgesamt 1 Wert" bzw. "Insgesamt <...> Wert e" ausgeben.
Einige würden das so schreiben:
Code : |
unsigned char anzahl_werte=0;
//...
if (anzahl_werte==1)
printf("Insgesamt 1 Wert\n");
else
printf("Insgesamt %2i" Werte\n",anzahl_werte); |
|
Ich mache das so:
Code : |
//(...)
printf("Insgesamt %2i Wert%s\n",anzahl_werte,(anzahl_werte==1)?"":"e"); |
|
Ob guter Stil oder nicht, es ist auf jeden Fall kürzer... Wie mir mal jemand sagte, Informatiker sind von Natur aus faul.
Zitat :
|
void machwas( void );
...
/* aufrufen */
(void) machwas ();
Ausreichen würde allerdings völlig
machwas();
|
Das kannte ich noch nicht. Eigentlich gar nicht doof, weil man auf den ersten Blick den Rückgabewert/Typ der Funktion erkennt.
Zitat :
|
Also, so langsam wird es klar: gerade bei C führen enorm viele Wege nach Rom, oft selbst die Irrwege.
|
C ist fürchterlich kompliziert, aber ich will endlich mal eine Sprache lernen die auch etwas bringt. Und in Delphi und Co. kann man keine µCs programmieren.
Zitat :
|
C ist eigentlich einfach, diesen ganzen Kram kannst du getrost vergessen, wenn du nur für eine Plattform entwickelst.
|
Da ich die Win-API nutze ist das mit den mehreren Plattformen sowieso erstmal hinfällig, also werd ich die Finger vom Schalter -ansi lassen. Was -wall und Co. betrifft, mal sehen. Ist es für einen Anfänger empfehlenswert irgendwelche Extraparameter anzugeben?
Zitat :
| Das ist OK so, aber der Ordnung halber muss da noch eine break-Anweisung rein, auch wenn nach dem return Schluss ist: |
Hab ich geändert, Danke für den Hinweis.
Zitat :
| Schau dir mal das Disassembling eines einfaches Programmes an, was dort vor main alles abläuft . |
Ja, eigentlich keine schlechte Idee. Gut das ich etwas AVR-Assembler kann, mal sehen ob ich mir das mal antue.
Meine hoffentlich letzte Frage:
Zitat :
| Genaugenommen steht dort folgendes:
char const * const string1 = "1+\0";
char const * const string2 = "2+\0";
case x: return string1; break;
|
Wie muss man das "entziffern"?
Ich lese das so: string1 ist ein konstanter (das erste const) Zeiger (*) auf ein konstantes (das zweite konst) Array aus Buchstaben (char) welches die ASCII-Werte '1', '+' und '\0' enthält. (Ein String ist ja in C nichts anderes als ein Array.)
Richtig "gelesen"?
Offtopic :
|
Zitat :
| Der Stackpointer wird ja auch ständig verändert! Beim Aufruf einer Funktion landet dort die Rücksprungadresse und der Pointer wird geändert. Bei jedem "Push" und "Pop" ändert er sich. Zudem kommt, dass größere Parameter/viele Parameter für Funktionen oft auch den Stack nutzen, um übergeben zu werden. Also der ist ständig am wachsen und schrumpfen . |
Argh, erinnerre mich nicht dran...
2,5k Zeilen (ohne Leerzeilen/Sprungmarken/...) AVR-Assembler wollen erstmal debuggt sein, ein push oder pop vergessen und es endet im Chaos.
Das nächste mal nehme ich C.
|
Wie gesagt, nochmal vielen Dank für die Mühe.
edit: Vor lauter C hab ich deutsch verlernt.
[ Diese Nachricht wurde geändert von: bastler16 am 23 Aug 2009 17:01 ]
|
BID = 629062
DonComi Inventar
Beiträge: 8605 Wohnort: Amerika
|
Tach,
Zitat :
|
Ist es für einen Anfänger empfehlenswert irgendwelche Extraparameter anzugeben?
|
Wenn man direkt in der shell beginnt ist das alles kein Problem.
Bei einer IDE wüsste ich nicht auf Anhieb, wie ich eigene Schalter/Parameter an die Toolchain (Compiler, Linker, ...) übergeben könnte.
In den meisten IDEs ist es wohl nicht nötig, da die ja fast alles übernehme. Wenn du das Programm nur kompilieren, aber nicht linken willst, hängt die IDE im Falle von GCC z.B. automatisch ein -c an. Diese Aufrufe müsste man eigentlich auch sehen können. Dann pipen die die Ausgabe des Compilers an sich zurück und geben es selbst aus.
Soll jedoch danach noch gelinkt werden, ändert auch die IDE den Aufruf dementsprechend.
Es sollte aber irgendwo die Möglichkeit geben, benutzerdefinierte zusätzliche Parameter anzugeben. Ist aber i.d.R. nicht nötig.
Zitat :
|
printf("Insgesamt %2i Wert%s\n",anzahl_werte,(anzahl_werte==1)?"":"e");
|
Sehr schön, für sowas ist das toll. Gibt aber noch fiesere Konstrukte, die man damit bauen kann.
Übrigens würde hier ein Byte reichen ('e'), dann muss kein Zeiger übergeben werden:
fprintf(stdout, "Insgesamt %i Wert%c\n", i,( (i==1) ? ('\0') : ('e') ));
Soviel zu den Wegen...
Zitat :
|
Ich lese das so: string1 ist ein konstanter (das erste const) Zeiger (*) auf ein konstantes (das zweite konst) Array aus Buchstaben (char)
|
Exakt !
Würde C strikter mit den Typen umgehen (was manchmal toll wäre ), müsste deine Funktion dann auch so deklariert sein:
char const * const note(float const wert);
Edit: Damit wären wir auch wieder bei den impliziten Typumwandlungen
...
_________________
[ Diese Nachricht wurde geändert von: DonComi am 23 Aug 2009 17:26 ]
|
BID = 629065
DonComi Inventar
Beiträge: 8605 Wohnort: Amerika
|
Was vielleicht anfangs nicht so rüberkam bzw. für Missverständnisse gesorgt hat, ist das Schlüsselwort auto.
Erwähnt habe ich es, weil es um Dinge ging, die implizit im Hintergrund stattfinden, eben, dass beispielsweise
int wert;
gleichbehandelt wird wie
auto int wert;
Damit wird dem Compiler nur gesagt, dass er sich bitte die Speicherklasse für die Variable aussuchen soll.
Findet das außerhalb einer Funktion statt wird es meist eine statische Variable sein, innerhalb landet sie i.d.R. auf dem Stack oder aber bei AVRs unter Umständen in Arbeitsregistern.
Also, auto wird implizit angenommen, auch wenn es nicht angegeben ist, bzw. wenn nichts anderes angegeben ist.
Wählt man als Speicherklasse z.B. register aus, so teilt man dem Compiler mit, dass man diese Variable gern im Register verwaltet haben möchte. Dem Compiler bleibt aber letztendlich die Entscheidung, ob er das tut oder nicht.
So, ich hoffe, damit ist die Verwirrung ein wenig kleiner geworden.
Stack hin oder her, ich kann nicht mehr ...
_________________
|
|
Zum Ersatzteileshop
Bezeichnungen von Produkten, Abbildungen und Logos , die in diesem Forum oder im Shop verwendet werden, sind Eigentum des entsprechenden Herstellers oder Besitzers. Diese dienen lediglich zur Identifikation!
Impressum
Datenschutz
Copyright © Baldur Brock Fernsehtechnik und Versand Ersatzteile in Heilbronn Deutschland
gerechnet auf die letzten 30 Tage haben wir 19 Beiträge im Durchschnitt pro Tag heute wurden bisher 34 Beiträge verfasst © x sparkkelsputz Besucher : 182425807 Heute : 4854 Gestern : 5459 Online : 378 29.11.2024 22:46 2 Besucher in den letzten 60 Sekunden alle 30.00 Sekunden ein neuer Besucher ---- logout ----viewtopic ---- logout ----
|
xcvb
ycvb
0.0627410411835
|