C Ausgabe von char * in der Konsole fehlerhaft (meine ersten Schritte in C)

Rossibaer

Lieutenant
Registriert
Apr. 2007
Beiträge
754
Hallo zusammen,

ich spiele z.Z. ein bißchen mit C rum (Tiny C Compiler) und wollte mal eine Eingabe von Zeichen in einer verketteten Liste speichern und anschließend aus dieser Liste wieder auf der Konsole zurückgeben. Einlesen funktioniert soweit. Bei der Ausgabe erscheint jedoch der 1. Buchstabe + 3 komische Zeichen, dann der 2. Buchstabe gefolgt von 3 komischen Zeichen ... Kann mir einer erklären was ich bei der Ausgabe denn falsch mache? Der Tiny C Compiler meldet jedenfalls keine Fehler. Das Programm scheint auch so zu laufen. Debuggen kann ich momentan noch nicht, da ich das "Programm" im Notepad geschrieben habe und dann per Kommandozeile mit "tcc hello_world.c -o hello_world.exe" unter Windows 10 kompiliere. Einen ähnlichen - wenn nicht sogar den gleichen - Effekt hatte ich bei der Anzeige der Eingabe als ich getch() statt getchar() zum Einlesen verwendet hatte und den Wert mit printf() wieder ausgab.

Hier der vollständige Code:

Code:
#include <stdio.h>

typedef struct _node
{
	char value;
	struct _node *next;
	struct _node *previous;
} node;

node *first_node = NULL;
node *last_node = NULL;

node *add_char(char value)
{
	node *current = (node *) calloc(1, sizeof(node));
	current->value = value;
	if(first_node == NULL)
	{
		first_node = current;
		last_node = current;
		return current;
	}
	last_node->next = current;
	current->previous = last_node;
	last_node = current;
}

char *get_text(void)
{
	node *current = first_node;
	int size = 50;
	char *text = (char *) calloc(size, sizeof(char));
	if(current == NULL) return text;
	int indx = 0;
	while(current != NULL)
	{
		if(++indx > size);
		{
			int new_size = size * 2;
			char *tmp = (char *) calloc(new_size, sizeof(char));
			int i;
			while(*text != '\0')
			{
				*tmp = *text;
				tmp++;
				text++;
			}
			char *cleanup = text;
			text = tmp;
			free(cleanup);
			size = new_size;
		}
		*text = current->value;
		current = current->next;
	}
	size = indx;
	char *ret_text = (char *) calloc(size + 1, sizeof(char));
	while(*text != '\0')
	{
		*ret_text = *text;
		ret_text++;
		text++;
	}
	free(text);
	return ret_text;
}

int main(void)
{
	char input;
	
	while((input = getchar()) != '\n')
	{
		printf(&input);
		add_char(input);
	}
	
	char *text = get_text();
	printf(text);
	free(text);
	
	printf("\n\nPress any key to continue... ");
	getchar();

	return 0;
}

Ich habe das Gefühl das ich mich mit den Pointern verhauen habe. Wäre super wenn ein C Guru mal drüber schaut und mir einen Hinweis für die Lösung geben könnte.

Danke das du meine Frage soweit schon gelesen hast...

Was mir noch so auf dem Herzen liegt: Tipps Richtung anderen Compiler oder sogar eine IDE verwenden, sind nicht hilfreich! Ich will mir das Leben absichtlich schwer machen. :D Und über die Schönheit des Codes kann man auch streiten...
 
Du willst dir das Leben mit absicht schwer machen, bist beratungsresistent aber andere sollen dir dann helfen?

Seltsame Logik! :freak:

greetz
hroessler
 
@hroessler: Beratungsresistent? Gegen welche deiner Beratungen wurde denn resistiert?

@Rossibaer: Schau dir Zeile 74 noch mal genauer an. Aber ein Debugger wäre definitiv besser. Dann kannst du deinen Code Schritt für Schritt durchgehen und siehst sofort wo/wie etwas schief geht.

Gruß
BlackMark
 
BlackMark schrieb:
@hroessler: Beratungsresistent? Gegen welche deiner Beratungen wurde denn resistiert?

Siehe letzten Absatz nach dem Code

@TE
Eine IDE mit ordentlichem Debugger zu nutzen hat einen höheren Lerneffekt, als dieses Rumstochern was du aktuell versuchst.
Verstehe auch den Sinn nicht, sich das Leben extra schwer zu machen. Zudem würde ich aktuell auch lieber mit Rust anstatt mit C anfangen.
Nicht negativ verstehen, aber das ist nur meine Meinung ^^.
 
Zuletzt bearbeitet:
Ich kann durchaus nachvollziehen, dass man sich das Leben extra hart machen will, um daraus zu lernen. Mit Texteditor und per Command Line Compilen ist definitiv auch ein Weg, der dazu führt, dass man nicht so sehr auf die ganzen Funktionen einer IDE angewiesen ist, zumindest während man noch lernt. Auf eine IDE umzusteigen, sobald man die Basics kann, wäre dann natürlich der nächste Schritt. So habe ich das Ganze verstanden.
Beratungsresistenz bezüglich des Themas wäre zu sagen, dass man printf auf jeden Fall so hernehmen will und die Zeile 74 auf keinen Fall ändern will.

Dem TE abzuraten den harten Weg zu gehen ist Off-Topic und auch nicht unbedingt sinnvoll, nicht jeder Mensch lernt gleich, lasst ihn doch mit Texteditor die Basics lernen, das schadet sicher nicht.
Ich würde nur einen Debugger (zB GDB als Command Line Tool) empfehlen, dann geht man immer noch den harten Weg, lernt zusätzlich noch wie man per Command Line debugged und genießt die Vorteile die ein Debugger bringt, sowohl bei Code Verständnis als auch bei der Fehlersuche.

Gruß
BlackMark
 
Hallo zusammen,
hätte jetzt nicht mit vielen Antworten gerechnet...

Danke schon mal für die Anteilnahme.

@hroessler und shio:
Ich bin kein unbeschriebenes Blatt, was das Programmieren angeht. Meine Wohlfühlzone ist C# .Net und das seitdem es auf den Markt kam. Habe in diversen Unternehmen/Projekten gearbeitet. Kenne die Vorzüge von Debugger/IDE etc. Vorher habe ich mit verschiedenen anderen Sprachen gearbeitet.

C hatte ich das letzte Mal vor ca. 20 Jahren während der Ausbildung, seitdem zwar immer mal wieder in fremden Code geschaut, aber nie was eigenes gemacht. Dachte mir ich geh mal zurück zu den Anfängen, da ich zwar weiß, wie schön eine IDE ist, aber leider verliert man schnell die Hintergründe, warum etwas nicht funktioniert, wenn man dann das Ergebnis einer Analyse frisch fromm fröhlich frei von der IDE direkt um die Ohren gehauen bekommt.

Die Entscheidung ohne IDE ist bewußt und ich weiß auch, dass es damit härter wird. Aber - gleichzeitig trennt sich dann auch die Spreu vom Weizen, was die Antworten angeht, denn hier ist nicht die Bedienung von einem Programm X gefragt, sondern die Hintergründe warum der Code so tickt, wie er tickt.

Mich hat halt interessiert, warum der Code eine fehlerhafte Ausgabe erzeugt. Nicht wie ich eine IDE XYZ bedienen soll. Ich sehe das mal als Training an, denn oft habe ich es mit fremden Code zu tun, der auf einer Machine beim Kunden läuft, wo ich nicht eben mal so einen Debugger anhängen kann, den Fehler aber bei mir nicht in der klinischen Testumgebung oder Entwicklungsumgebung reproduzieren kann. Läßt sich jetzt streiten, wie es zu Fehlern beim Kunden kommt, die nicht bei der Entwicklung zu sehen sind. Aber das ist ein anderes Thema...

@BlackMark, Hancock:
Die Zeile ist erstmal vordergründig in Ordnung, da hier die Anzeige auch so ankommt, wie ich es erwartet hätte. Ist zwar sehr merkwürdig, wie ich das geschrieben habe, aber ich bin kein C Profi und wollte nur eben mal in der Konsole den Buchstaben ausgeben, den ich gerade über die Tastatur eingegeben habe. Das funktioniert auch so wie gedacht. Mein Problem liegt im Bereich der Zeilen 78 und 79. Quasi der Return Wert von get_text() und dessen Ausgabe auf der Konsole. Sorry, dass ich das in meinem Anfangstext nicht genauer beschrieben hatte... Mein Fehler!

Jetzt meine Vermutung, korrigiert mich bitte, wenn ich falsch liege: Angenommen char wäre sowas wie ein byte, schön ASCII mäßig. getchar() liefert aber einen int (so zumindest in der stdio.h definiert). Angenommen das wären 4 Byte. Gehe ich davon aus, dann ist das 1. Byte das Zeichen, was ich ausgeben will und die restlichen 3 Byte kommen von dem int und werden jeweils ebenfalls als Zeichen interpretiert. Diese Zeichen sind dann die komischen Zeichen während der Ausgabe. Mit folgender Eingabe "abcd" erreiche ich dann eine Ausgabe ala "aXXXbXXXcXXXdXXX" wobei das X für ein beliebiges Zeichen steht. Das XXX folgt dann einem Muster, scheinen quasi immer die gleichen Zeichen zu sein. Wo ich nicht so mitkomme, ist die Tatsache, dass ich bei der ersten Ausgabe des gedrückten Zeichens nur dieses sehe ohne die 3 restlichen Zeichen, aber dann bei Ausgabe der ganzen Liste (Zeile 79) dann wieder diesen Zeichensalat. Als ich statt getchar() einmal getch() verwendet hatte, wurde der Zeichensalat sowohl während der Eingabe als auch der Ausgabe angezeigt. Und da hätte ich gern gefragt, ob mir das einer genauer erklären kann...

PS: Ich wollte niemanden mit meiner Aussage, dass ich ungern eine IDE verwenden will, zu nahe treten. Ist schon komisch, dass man eine Frage stellt, ein paar Randbedingungen nennt und schon in der ersten Antwort als beratungsresistent denunziert und angegriffen wird! Aber das Leben ist schön, also bitte wieder runterkommen, friedlich miteinander kommunizieren und ggfs. eine kleine Nuss knacken. ;)

PPS: BlackMark, Hancock es ist wieder schön von euch was zu lesen. Ich war jetzt lange nicht mehr aktiv hier im Forum. Aber dass ihr noch da seid, freut mich sehr!

PPPS: hroessler, deine Meinungen schätze ich auch sehr, war aber schockiert, wie du gleich drauf gehauen hast... :(
 
Na ja: Zeile 74 ist halt falsch (und sau gefährlich (Hackbarkeit und so)), printf(char*format,...) will halt zuerst einen Formatierstring, und du übergibts ein int*, was so nicht gut ist. Was richtig wäre, ist ein printf("%c", input). So gibt printf ein einzelnes Zeichen aus.

Genau das gleiche Problem hast du in Z. 79. Geb mal testweise ein %s in dein Program aus.

Zum Problem: Z. 53: Musst du dann nicht text inkrementieren? Oder am besten gleich text[indx] schreiben.

Weitere Probleme ab 50 Zeichen: Z. 41: int i wird nie verwendet. Im weiteren Verlauf veränderst du Zeiger und gibt's welche frei, die nicht auf den Anfang des angeforderten Bereich zeigen. Lass die Schleife zum kopieren weg und nutz memcpy.

Hinweise: Verwende doch statt while for und inkrementier da indx.
 
Schau dir trotzdem die Zeile 74 (und in weiterer Folge auch 79) nochmal ganz genau an und vergleiche mit der Definition von printf, denn selbst wenn Zeile 74 funktionieren sollte, dann ist das undefined behavior und funktioniert nur zufällig. Du verwendest printf falsch, das ist dein Fehler. Dass getchar einen int liefert ist egal, das wird in Zeile 72 durch einen impliziten cast auf char schon "behoben".
Achte auch darauf, dass deine Strings in allen Fällen immer Null-terminated sind, das Verhalten das du siehst deutet nämlich darauf, dass das nicht der Fall ist, wobei das auch einfach das undefined behavior von printf sein kann, sowas würde man halt mittels Debugger testen.

Edit: Viel zu langsam...

Gruß
BlackMark
 
Hallo zusammen,

vielen Dank für eure Tipps. Wie sich herausstellte, habe ich nicht nur Probleme bei der Verwendung von Printf und meinen Versuchen Speicher zu kopieren. Bin erstaunt, was alles für Fehler in nur 80 Zeilen Code stecken können...

1. printf Fehler durch Verwendung von printf("%c", input) in Zeile 74 und printf("%s", text) in Zeile 79 behoben.
2. Speicher kopieren mit inkrementieren der Zeiger durch memcpy ersetzt (Zeilen 42 - 47 und Zeilen 58 - 63)

desweiteren
3. add_char: fehlendes return, wenn die Liste bereits einen node enthielt (Zeile 26)
4. get_text: if Bedingung hat ein Semikolon, wodurch der eigentliche Block immer durchlaufen wurde und die Prüfung sinnlos war (Zeile 37)

ein wenig Refactoring habe ich auch noch gemacht, d.h. den Code zum Kopieren und Vergrößern des text Speichers habe ich in eine eigene Funktion resize_text ausgelagert, da ich ihn an 2 Stellen brauchte (einmal beim Vergrößern des Speichers während des Aufbauens und dann beim Zurückgeben des char "Arrays")

Aktuell frickel ich an der Eingabe: getchar() scheint die Eingabe zu puffern bevor es da weiter geht, mit getch() wartet er und läuft erst nach einem Tastendruck weiter. getch() gibt unter Windows bei Enter ein '\r' zurück, ich prüfe aber auf '\n'.

Ebenso wird bei der Ausgabe des Textes scheinbar mein Puffer abgeschnitten, d.h. der Inhalt nur bis zum ersten Kopiervorgang wird angezeigt. Ich habe die Anfangsgröße auf 4 eingestellt, so wie es .Net mit List<> macht. Wenn ich also dann 123456 eingebe, wird nur 1234 angezeigt. Entweder ich habe bei den Kopier und Zuweisungsaktionen in get_text nach dem Vergrößern eine Lücke mit einem '\0' drin, wodurch printf alles danach verwirft oder ich schneide beim resize_text vor dem return in get_text zuviel ab.

Vielen Dank nochmal an alle, die sich beteiligt haben.

PS: Ich hatte mal testweise den gcc in der Kommandozeile verwendet und da die Optionen -Wall und -pedantic mit beim Kompilieren verwendet, Holla die Waldfee, da kam viel dabei raus. Bei tcc gibt es zumindest -Wall. Bin mir noch nicht sicher ob ich auf den gcc umsteige oder weiter mit tcc spiele. Zumindest lässt sich eine Debugversion mit -g vom tcc kopilieren mit der auch gdb umgehen kann.
 
Konsoleneingabe Zeichenweise ist schon immer frickelig.

Dass dein Puffer abgeschnitten wird, bedeutet, dass du womöglich den falschen Zeiger behältst. Oder aber tatsächlich falsch trimmst: Debugger.

Nehm gcc, -Wall ist Pflicht, und nehm noch -ggdb3 dazu, dann bekommst du wirklich perfekte Debuginformationen. tcc ist zum programmieren Lernen nicht sonderlich geeignet, siehe Compiler-Warnungen.
 
Hallo zusammen,

ja zeichenweise Konsoleneingabe ist wirklich frickelig.

Der Text wurde abgeschnitten bzw. das Programm hatte noch Fehler, weil ich an 2 Stellen falsche Größen verwendete. Zum einen die Zeile 37 schon wieder. Da war ja was! Ich hatte "übersehen" dass die Entscheidung "++indx > size" dafür sorgte, dass über den Puffer geschrieben wurde, richtig war jetzt die Prüfung mit "++indx > size - 1". Hatte mich gewundert, warum beim free() der Debugger (oder das Programm) aus dem Tritt geriet und da scheinbar hängen blieb.

Beim abschließenden Trimmen hatte ich das Problem, dass der Puffer nicht groß genug war und somit der String nicht mehr mit \0 endete.

In meiner neuen resize_text Funktion hatte ich noch das Problem, dass hier immer die Originalgröße des Puffers genutzt wurde, ich aber beim Trimmen am Ende von get_text quasi den Puffer verkleinern wollte.

Nachdem ich nun diese Fehler behoben habe, scheint das Programm, wie erwartet, zu laufen.

Hier der neue Code für diejenigen, die es interessiert ...

Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>

typedef struct _node
{
	char value;
	struct _node *next;
	struct _node *previous;
} node;

node *first_node = NULL;
node *last_node = NULL;

void add_char(char value)
{
	node *current = (node *) calloc(1, sizeof(node));
	current->value = value;
	if(first_node == NULL)
	{
		first_node = current;
		last_node = current;
	}
	last_node->next = current;
	current->previous = last_node;
	last_node = current;
}

char *resize_text(const char *source, int size, int new_size)
{
	char *target = (char *) calloc(new_size, sizeof(char));
	if(source != NULL)
	{
	    int copy_size = size > new_size ? new_size : size;
	    memcpy(target, source, copy_size * sizeof(char));
	}
	return target;
}

char *get_text(void)
{
	node *current = first_node;
	int size = 4;
	int indx = -1;
	char *text = resize_text(NULL, 0, size);
	char *tmp;
	if(current == NULL) return text;
	while(current != NULL)
	{
		if(++indx > size - 1)
		{
			int new_size = size * 2;
			tmp = text;
			text = resize_text(text, size, new_size);
			free(tmp);
			size = new_size;
		}
		text[indx] = current->value;
		current = current->next;
	}
	tmp = text;
	text = resize_text(text, size, indx + 2);
	free(tmp);
	return text;
}

int main(void)
{
	char input;
    char *text;
	printf("Give me some text: ");

	while((input = getch()) != '\r' && input != '\n')
	{
		printf("%c", input);
		add_char(input);
	}

	if(input == '\r')
        printf("\n");

	printf("You entered text: ");
	text = get_text();
	printf("%s", text);
	free(text);
	printf("\n");

	printf("Press any key to exit... ");
	getch();

	return 0;
}

Vielen Dank für eure wertvollen Tipps...

Grüße
Rossibaer
 
Hallo asdfman,

jede Kritik ist willkommen! Egal ob positive oder negative Kritik. Immer nur her damit.
Aus Fehlern lernt man mehr als aus Erfolgen... Also gib bitte deinen Senf dazu.

Grüße
Rossibaer
 
Zurück
Oben