PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : For Schleife mit Hindernissen



Siro
10.03.2014, 21:33
Bevor ich wieder eine Panikattacke auslöse weil ich das Wort Pascal benutze
würde ich mich über sinnvolle Äußerungen zu meinem C Problem freuen ;)


Ich hab grad ein Modülchen von Pascal nach C umgeschrieben für ein LCD Display.
Nix aufregendes, aber erstaunt war ich doch, als die Texte nicht komplett ausgegeben wurden.
Immer nur die vordere Hälfte.
Der Fehler war mir erstmal völlig rätselhaft.
StrLen liefert das richtige Ergebnis nämlich 4
es wurden aber nur 2 Zeichen ausgegeben.


/*----------------------------------------------*/
int StrLen(const char *p)
{ int len = 0;

while (*p++) len++;
return len;
}
/*----------------------------------------------*/
void LcdWriteChar(const char c)
{
/*... gib Zeichen auf dem LCD aus ...*/
}
/*----------------------------------------------*/
void LcdWriteStr(const char* s)
{ int i;

for (i=0; i<StrLen(s); i++)
{
LcdWriteChar(*s++);
}
}
/*----------------------------------------------*/
int main(void)
{
LcdWriteStr("ABCD");
}


Dann hab ich die Funktion umgeschrieben und siehe da es funktioniert


void LcdWriteStr(const char* s)
{ int i,len;

len = StrLen(s);
for (i=0; i<len; i++)
{
LcdWriteChar(*s++);
}
}

Als ich mir den Assemblercode ansah, wurde der Fehler klar.
Er ruft bei jedem Durchlauf, also für jedes Zeichen die Funktion StrLen auf,
aber jedesmal mit dem incrementierten Pointer :confused:
Wie kommt das denn ? Das ist doch eine Konstante und die StrLen braucht er doch nur
einmal aufrufen. Da kann sich doch nichts ändern.
Und es Ist auch egal ob ich da Const benutze oder nicht, der Code bleibt exakt identisch.

Eigentlich sind das ja schon 2 Fragen. Wozu das Const in der Funktion. Ich hätt gedacht daurch kann der Compiler eventuelle Optimierungen vornehmen.

Danke Euch im Voraus für Infos

Peter(TOO)
10.03.2014, 23:17
Hallo

*s++
sind eigentlich zwei Befehle!
*s und s++
Es wird also das Zeichen auf welches s zeigt geholt und danach wird s um eins erhöht.

Da s erhöht wird, wird auch der String immer kürzer.
Sagen wir mal im Speicher liegt folgendes
'A','B','C','D',0
Dann zeigt s auf 'A' und der string ist 4 zeichen lang (0 markiert das Ende).
Nach s++ zeigt s auf 'B', der String ist jetzt nur noch 3 Zeichen lang ...

const char* s
bedeutet, dass s auf einen Speicherbereich zeigt, welcher nicht beschrieben werden kann.
s darf aber verändert werden.

const char* const s
dann wäre auch s gegen Veränderung geschützt und der Compiler könnte optimieren. Aber dann geht s++ nicht mehr .....

Da aber ein String in C mit einem 0 am Ende markiert ist, was auch StrLen() so auswertet, kann man das Ganze viel einfacher schreiben.


/*----------------------------------------------*/
void LcdWriteStr(const char* s)
{
while (*s)
{
LcdWriteChar(*s++);
}
}
/*----------------------------------------------*/

MfG Peter(TOO)

Klebwax
10.03.2014, 23:39
Ich fang mal von hinten an:


Wozu das Const in der Funktion. Ich hätt gedacht daurch kann der Compiler eventuelle Optimierungen vornehmen.

Das const sagt dem Benutzer der Funktion, daß die Funktion diese Variable nicht ändern wird. Andere String-Funktionen tun das, diese aber nicht. Das hat mit optimieren nichts zu tun. Wenn du selber weißt, was die Funktion tut, kannst du das getrost weglassen.


Er ruft bei jedem Durchlauf, also für jedes Zeichen die Funktion StrLen auf, aber jedesmal mit dem incrementierten Pointer

Das muß er auch tun. s wird ja in der Schleife verändert (s++), also muß strlen neu berechnet werden. Und jetzt doch zum Optimizer: wenn s innerhalb der loop konstant wäre, könnte der Optimizer die Berechnung von strlen aus der loop nehmen und außerhalb berechnen. Dein for()


for (i=0; i<StrLen(s); i++)

ist einfach falsch, zumindestens beschreibt es nicht, was du meinst.

Das ganze macht man eigentlich viel einfacher, so in der Richtung:


while(*s) {
....
s++;
}
Und Funktionen wie strlen() o.ä. gibts fertig in der libc, die schreibt man nicht selbst

MfG Klebwax

Siro
11.03.2014, 06:40
Moin Leute,

wie ich sehe wart Ihr schon fleissig und habt mich ein bischen aufgeklärt.

Ja, Ihr habt beide recht, das kann ich doch ganz anders schreiben und brauche strlen garnicht :p
Manchmal hat man echt eine Brett vor den Augen bzw. fehlt mir bei der Stringverarbeitung in C noch die Übung....
Wird sofort so übernommen und vielen Dank für die Infos.



Und Funktionen wie strlen() o.ä. gibts fertig in der libc, die schreibt man nicht selbst

Eigentlich schon, hast Du recht, aber ich benutze generell KEINE Bibliotheken, nichteinmal stdlib, damit
hat sich das Thema SOUP bei der Softwaredoku erledigt und ich habe den gesamten Source-Code zur Verfügung.

Na dann einen schönen Tag Euch noch.
Siro

shedepe
11.03.2014, 12:20
Ist SOUP nicht eher ein Argument dagegen alles selber zuschreiben. Die C Standard Bibliothek wird vermutlich viel mehr kritischen Anwendungen eingesetzt und getestet als deine eigenen Routinen.

Peter(TOO)
11.03.2014, 17:27
Hallo Siro,

Eigentlich schon, hast Du recht, aber ich benutze generell KEINE Bibliotheken, nichteinmal stdlib, damit
hat sich das Thema SOUP bei der Softwaredoku erledigt und ich habe den gesamten Source-Code zur Verfügung.

Also woher die Standardbibliotheken kommen weisst du doch eigentlich?

Zudem hat ein vernünftiger Compilerhersteller auch entsprechende Testsuiten. Die kosten zwar ein Heiden Geld, finden aber praktisch alle Fehler.

Desweiteren hat C einen grossen Vorteil! Du kannst auch die Standardbibliotheken durch eigene ersetzen und somit die gebräuchlichen Funktionsnamen verwenden., was den Code wesentlich lesbarer macht.
Zudem ist es kein Problem, den Sourcecode der Standardbibliotheken zu bekommen.

MfG Peter(TOO)

Siro
13.03.2014, 14:52
Hallo Peter,
ehrlich gesagt ich weis nicht wo do Standard Bibliotheken her kommen, ich hab auch noch nie eine benutzt.


Das ist sicher eine Einstellungssache was man benutz oder auch nicht.

Deshlab mal eine kurze Übersicht warum ich ungerne Bibliotheken benutze:

Was ich nicht benutze, brauche ich auch nicht zu dokumentieren oder zu verstehen.
Wenn MEINE Funktion fehl schlägt bin NUR ich selber dafür verantworlich, siehe meine Lcd Ausgabe die nicht richtig lief.

Sicherlich sollten die vorgegebenen Funktionen in den Standardbibliotheken funktionell und gut getestet sein,
das will ich auch nicht annähernd anzweifeln. Wer schreibt die eigentlich ?

Aber:
Ich blicke gerne durch. Möchte den Code verstehen.
Ich habe gerne ALLE Dateien in MEINEM Projektordner.
Solange ich keine fremden benutze geht das auch einwandfrei.
Bei der Datensicherung wird lediglich MEIN Order gesichert , da ist ALLES drin was für ein neues Compilat auf irgendeinem Rechner erforderlich ist,
sofern der Compiler installiert ist.

Aber sobald ich <include.....> mit benutze geht das Chaos schon los.
Welche Datei aus welchen Ordner auf welcher Platte benutzt er denn jetzt ?
Wie bekomme ich die denn mit in meine Datensicherung ?
Wo muss ich die auf einem anderen System hinpacken, damit ich es wieder kompilieren kann.

Das ist für mich ein ziemlich undurchschaubares Unterfangen, geschweige denn das ich das irgendwie dokumentieren könnte oder will.
Ich bin eigentlich ständig auf der Suche nach irgendwelchen Dateien.

Das Suchprogramm "Everything" ist inzwischen mein täglich Brot geworden, wenn ich das hier erwähnen darf.

Ich hab z.B. auf meinem Rechner 57 mal stdarg.H drauf.
Das bewegt sich in Speichergrößen von 1K bis 5K Das soll alles das Gleiche sein ?
wenn ich da reingucke, bekomme ich eine Augenlähmung :) so würde ich das mal nennen.
Oft muss ich mit dem Cursor erstmal zählen wieviele Unterstriche es sind.
Aha, das waren 4, deshalb meckert er, ich hab nur 3 gemacht.

Und so ist es für mich wesentlich einfacher und vor allem Übersichtlicher mal eben eine Funktion selbst zu implementieren.
Einen Dreizeiler brauche ich auch nicht in zig Dateien aufzusplitten die irgendwie auch noch von einander abhängig sind.

Das ist natürlich meine ganz persönliche Einstellung.

Allein der Header string.H ist schon für mich unverständlich:
Zumal ich davon 64 Objekte auf meinem Rechner habe.
In manchen steht teilweise nur ein Kommetar, sehr sinnig......
andere sehen aus wie eine Unterstrichsammlungsbibliothek ;)

Siro

shedepe
13.03.2014, 15:39
Dann hast du doch bestimmt auch deinen Compiler selber geschrieben, sonst kannst du ja nicht sicher gehen, dass der Compiler wirklich den Binärcode erzeugt den er soll. (Die C - Standardbibliothek wird entweder von deinem System bereitgestellt oder von deinem Compiler hersteller und diese Leute wissen warum sie etwas so implementieren wie sie es machen. Oftmals wird das ganze deswegen so "kompliziert" gestaltet und Portierbarkeit bzw. Flexibilität zu ermöglichen). Wenn du Gründen der Portierbarkeit bzw. dem Compilieren auf einem frischen System sorgen machst, schau dir lieber ein vernüftiges Dependency und Makefilesystem wie Cmake an.

Versuch mal eine eigene printf Funktion auf einem anderen System zumlaufen zu bekommen ^^

Siro
13.03.2014, 16:58
Abspecken ist meine Devise, nicht nur am Bauch :) Mehr will ich auch garnicht dazu sagen.
auf jeden Fall habt Ihr mir hier wieder weitergeholfen und darum ging es und dafür danke ich Euch.
Siro

Peter(TOO)
13.03.2014, 16:59
Hallo Siro,

Was ich nicht benutze, brauche ich auch nicht zu dokumentieren oder zu verstehen.
Wenn MEINE Funktion fehl schlägt bin NUR ich selber dafür verantworlich, siehe meine Lcd Ausgabe die nicht richtig lief.
Also die Standard-Bibliotheken sind zur genüge in Handbüchern dokumentiert.


Sicherlich sollten die vorgegebenen Funktionen in den Standardbibliotheken funktionell und gut getestet sein,
das will ich auch nicht annähernd anzweifeln. Wer schreibt die eigentlich ?
Das macht der Compilerhersteller, dadurch nutzen die auch Compilerspezifische Optimierungen.



Oft muss ich mit dem Cursor erstmal zählen wieviele Unterstriche es sind.
Aha, das waren 4, deshalb meckert er, ich hab nur 3 gemacht.
Da machst du grundsätzlich etwas falsch!
Alle normalen Bibliotheks-Funktionen haben keinen Unterstrich am Anfang.
Alte Bezeichner und nicht dem ANSI-Standard entsprechende Bezeichner (Erweiterungen durch den Compilerthersteller) beginnen mit einem Unterstrich.
Zwei Unterstriche am Anfang sind reserviert für Compilerinterne Dinge.
In deinem Code sollten also gar keine Bezeichner mit mehr als einem Unterstrich am Anfang auftauchen!



Allein der Header string.H ist schon für mich unverständlich:
Zumal ich davon 64 Objekte auf meinem Rechner habe.
In manchen steht teilweise nur ein Kommetar, sehr sinnig......
andere sehen aus wie eine Unterstrichsammlungsbibliothek ;)

Ich weiss nicht welche(n) Compiler du installiert hast.

Die meisten Compiler unterstützen mehrere Speichermodelle. Viele CUs unterstützen near und far Zeiger, je nach Anwendung kann man Speicherplatz sparen, wenn ein Zeiger nur 16-Bit belegt, anstatt 32 oder 64 Bit.
Da braucht es dann auch unterschiedliche Header, bzw. im Header müssen die aktuellen Compilereinstellungen abgefragt werden.
Für den Anfänger sieht das dann recht wirr aus, vermeidet aber viele Fehler.

MfG Peter(TOO)

Siro
13.03.2014, 18:02
Nabend Peter,
dank Dir nochmal für die Rückmeldung
die Bibliotheken sind gut dokumentiert und findet man überall im Netz, mit der Benutzung habe ich auch keine Probleme.
Wenn ich aber mal schauen will wie etwas implementiert wurde und dann auf folgende Konstukte in einer Datei stoße, bin ich echt überfordert
und das möchte ich nicht in meinem Code drin haben, weil ich es nicht verstehe, gebe ich gern zu.

Das muss ich ja auch nicht benutzen, ist mir klar, aber der Code steht ja dort nicht umsonst, also möchte ich ihn auch verstehen,
so wie ich auch mein Programm verstehe. (Meistens jedenfalls) :) Das muss mir jetzt auch keiner auseinanderpflücken, das ist nur ein Beispiel
aus einer Header Datei, die ich eben mal geöffnet habe.

Ich meine solche Strickmuster:



#if defined _GNU_SOURCE && defined __GNUC__
#define strdupa(__s) \
(__extension__ ({const char *__in = (__s); \
size_t __len = strlen (__in) + 1; \
char * __out = (char *) __builtin_alloca (__len); \
(char *) memcpy (__out, __in, __len);}))
#define strndupa(__s, __n) \
(__extension__ ({const char *__in = (__s); \
size_t __len = strnlen (__in, (__n)) + 1; \
char *__out = (char *) __builtin_alloca (__len); \
__out[__len-1] = '\0'; \
(char *) memcpy (__out, __in, __len-1);}))
#endif /* _GNU_SOURCE && __GNUC__ */



wenn ein Header z.B. wie folgt aussieht, gibt er für mich auch wieder Sinn, da braucht man garnicht drüber diskutieren. Ist ein Auzug vom Keil Compiler C51


extern char *strcat (char *s1, char *s2);
extern char *strncat (char *s1, char *s2, int n);

extern char strcmp (char *s1, char *s2);
extern char strncmp (char *s1, char *s2, int n);

extern char *strcpy (char *s1, char *s2);
extern char *strncpy (char *s1, char *s2, int n);

extern int strlen (char *);

extern char *strchr (const char *s, char c);
extern int strpos (const char *s, char c);
extern char *strrchr (const char *s, char c);
extern int strrpos (const char *s, char c);

extern int strspn (char *s, char *set);
extern int strcspn (char *s, char *set);
extern char *strpbrk (char *s, char *set);
extern char *strrpbrk (char *s, char *set);

extern char memcmp (void *s1, void *s2, int n);
extern void *memcpy (void *s1, void *s2, int n);
extern void *memchr (void *s, char val, int n);
extern void *memccpy (void *s1, void *s2, char val, int n);
extern void *memmove (void *s1, void *s2, int n);
extern void *memset (void *s, char val, int n);



Das Problem ist, wie Ihr schon sagt, die Vielfältigkeit und das macht es einem Bitschieber nicht grade einfach noch durchzublicken.

und 4 Unterstriche gibt es dann auch schon mal: Auszug stdarg.h vom GCC


#if !defined (_VA_LIST_) || defined (__BSD_NET2__) || defined (____386BSD____) || defined (__bsdi__) || defined (__sequent__) || defined (__FreeBSD__) || defined(WINNT)
/* The macro _VA_LIST_DEFINED is used in Windows NT 3.5 */

Peter(TOO)
13.03.2014, 18:56
Hallo Siro,




#if defined _GNU_SOURCE && defined __GNUC__
_GNU_SOURCE dürfte in einem Header definiert sein, wenn man bestimmte Projekte übersetzt.
__GNUC__ ist definiert wenn du mit dem GNU-Compiler übersetzt.
Der folgende Code wird nur verwendet wenn diese beiden Bedingungen erfüllt sind

#define strdupa(__s) \
(__extension__ ({const char *__in = (__s); \
size_t __len = strlen (__in) + 1; \
char * __out = (char *) __builtin_alloca (__len); \
(char *) memcpy (__out, __in, __len);}))
#define strndupa(__s, __n) \
(__extension__ ({const char *__in = (__s); \
size_t __len = strnlen (__in, (__n)) + 1; \
char *__out = (char *) __builtin_alloca (__len); \
__out[__len-1] = '\0'; \
(char *) memcpy (__out, __in, __len-1);}))
#endif /* _GNU_SOURCE && __GNUC__ */
strdupa(); und strndupa(); werden als inline Code definiert, verwenden aber compilerinterne Mechanismen (__builtin_alloca (); )




Das Problem ist, wie Ihr schon sagt, die Vielfältigkeit und das macht es einem Bitschieber nicht grade einfach noch durchzublicken.
und 4 Unterstriche gibt es dann auch schon mal: Auszug stdarg.h vom GCC ]


#if !defined (_VA_LIST_) || defined (__BSD_NET2__) || defined (____386BSD____) || defined (__bsdi__) || defined (__sequent__) || defined (__FreeBSD__) || defined(WINNT)
/* The macro _VA_LIST_DEFINED is used in Windows NT 3.5 */
Ist ein Hinweis, dass bei obigen Bedingungen (Es wird z.B. für Windows NT 3.5 übersetzt, ein anderes Macro verwendet wird.

Gerade der GNU-Compiler ist für unzählige CPUs, Betriebssysteme und Versionen vorhanden. Und jede Variante hat halt ihre speziellen Eigenheiten.
Wenn man es beherrscht, kann man in C Code schreiben, welcher unter unterschiedlichen Betriebssystem und CPUs funktioniert, das spart dann eine Menge Arbeit bei
der Pflege. Ich selbst habe z.B. Übertragungsprotokolle geschrieben, welche auf eine Micro-Controller (meist ein Hitachi H8) und unter Windows laufen. Dabei ist dann der
Sourcecode nur einmal auf der Festplatte abgelegt und wird in beiden Projekten eingebunden. Hat den Vorteil, dass beide Projekte immer automatisch auf dem selben
Stand sind, wenn man etwas ändert.
Bei mir waren dann oft noch zwei unterschiedliche Compiler im Spiel, VC für Windows und IAR für den H8. Da muss man dann vieles selber machen, was einem der GNU schon abnimmt, z.B. haben die #pragma unterschiedliche Syntax bei den beiden Compilern.


MfG Peter(TOO)

Siro
13.03.2014, 23:48
Hallo Peter,
Du gibst Dir wirklich sehr viel Mühe mein Nichtwissen aufzubessern.
Würde am liebsten Unterricht bei Dir nehmen. Ganz im Ernst.

Wiederverwendbaren Code zu schreiben ist ja auch Ziel meiner Bemühungen seit Jahren.
Es scheiterte aber daran das ich immer in Assembler programmierte. Da ist eigentlich nichts mehr
wiederverwendbar wenn man den Prozessor wechselt. So habe ich das auch möglichst vermieden.
Einmal PIC immer PIC. Inzwischen sind es die NXP Prozessoren mit CORTEX M3 Kern.
Um die Möglichkeiten voll auszuschöpfen muss man schonmal gucken was der Compiler für einen Code daraus macht.
Denn es gibt bestimmte Dinge die entsprechenden Assemblercode voraussetzen sonst funktioniert es nicht.
Das kann man dann aber trotzdem meist auch in C umsetzen wenn man dann weis wie.
Da fehlt aber halt noch reichlich Erfahrung.
Wenn ich soviel Zeit in "C" investiert hätte wie in die verschiedenen Assembler der verschiedenen Prozessoren....



shedepe schrieb:
Dann hast du doch bestimmt auch deinen Compiler selber geschrieben, sonst kannst du ja nicht sicher gehen, dass der Compiler wirklich den Binärcode erzeugt den er soll.

Da liegst Du garnicht so verkehrt, das ist so ungefär mein Anliegen und im Prinzip hab ich das schon in kleinem Maßstab umgesetzt.
Mein Assemblermacros sind inzwischen ähnlich einer Hochsprache und wurden immer wieder optimiert.
Da weis ich wirklich aufs Bit genau was raus kommt und welche Laufzeit dafür benötigt wird.
Leider nur für einen Prozessor anwendbar.

Hier muste ich aber auch schon feststellen: je universeller man sein möchte umso kryptischer wird der Code.


Na wie dem auch sei:
Aus Fehlern lernt man und ich hoffe ich mache noch viele Fehler......