C: fifo lesen

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 22.08.2018 15:21:38

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
22.08.2018 13:44:13
Dann habe ich auf Deine Anregung noch gefunden: http://marmaro.de/docs/studium/unix-phil/unix-phil.pdf
Das meintest Du doch, oder?
... und das: http://marmaro.de/docs/chaosseminar/

Und textuell noch das: http://marmaro.de/docs/master/schnalke-mmh.pdf ... falls du dir das geben willst. ;-)
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 22.08.2018 15:46:39

Oh, da hast Du ja ganz schön viel zu dem Thema verfasst. :THX:

Übrigens, die Videos in dem Link http://ulm.ccc.de/ChaosSeminar/2010/03_UnixPhil gibt's nicht mehr...

BTW - nochmal zu meinen shell-builtins: Damit kein falscher Eindruck entsteht, wollte ich nochmal erwähnen, dass die nur in bestimmten Fällen Performance-Vorteile bringen. Die Datei /proc/net/wireless z.B. ist drei Zeilen lang. Wenn ich jetzt aber z.B. die Namen aller Dateien in /usr/share/applications/ möchte, die "NoDisplay=true" beinhalten, lande ich mit builtins von der Performance her auf der Nase und nehme grep. Insofern sind die Unix-Tools im Allgemeinen schon auch von der Performance her zu bevorzugen, denn wenn sie auch bei kleinen Datenmengen ein klein wenig langsamer sein mögen, so sieht es bei größeren Datenmengen um Gößenordnungen anders aus.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 22.08.2018 21:26:39

Uff, signal handling geschafft... :D

Erst wollte ich einfach zusätzlich noch auf stdin lauschen - mit select geht das ja. Doch nachdem ich das implementiert hatte, habe ich gemerkt und ist mir wieder eingefallen, dass man nicht nach stdin eines im Terminal laufenden Processes mittels "echo /proc/pid/fd/0" pipen kann, weil diese fds immer zu denen des jeweiligen /dev/pts umgeleitet sind... :evil:
Aber eigentlich scheint es mir so jetzt auch eleganter. Ich nutze sigaction und etabliere vor dem loop eine Aktion, die beim Empfangen von SIGTERM (also "kill -15 pid") ausgeführt wird. Diese Aktion ist einfach das Umschalten einer toggle-Variable (ich mag toggle-Variablen :D ), auf deren Ursprungszustand der while-loop basiert. Wird sie also umgeschaltet, beendet sich der loop und dann werden die unter dem Loop stehenden Aufräumarbeiten ausgeführt. Man kann den Vorgang mit eingeschobenen printfs verifizieren.
(Das einzig Doofe ist, dass man die Variable signum benutzen muss, weil sich sonst gcc beklagt...)

Das kommt dann also bei "kill -15" heraus:

Code: Alles auswählen

/opt/scripts/dev$ ./test2xd
Hello, I am the signal handler.
Hello, I am the end of the main function.
Der Code mit den Test-printfs:

Code: Alles auswählen

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

static const char program_name[] = "test2xd";
static Display *dpy;
static int screen;
static Window root;
static XEvent ev;
static int finish;

static void
term_hdl (int signum)
{
	/* we have to use signum */
	switch (signum) {
		case SIGTERM:
			printf("Hello, I am the signal handler.\n");
			finish = 1;
	}
}

int
main(void) 
{
	char *display_name = NULL;
	char name[BUFSIZ];
	int fd;
	struct sigaction term_act;
	
	memset(&term_act, 0, sizeof(term_act));

	dpy = XOpenDisplay(display_name);
	if (dpy == '\0') {
		fprintf(stderr, "%s:  unable to open display '%s'\n",
				program_name, XDisplayName (display_name));
		exit(2);
	}
	root = RootWindow(dpy, screen);

	/* fd = open("/tmp/dwmblocks_fifo_intern", O_RDWR | O_NONBLOCK); */
	fd = open("/home/user/fifo", O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		perror("open");
		exit(1);
	}

	term_act.sa_handler = term_hdl;
	if (sigaction(SIGTERM, &term_act, NULL) < 0) {
		perror("sigaction");
		exit(EXIT_FAILURE);
	}

	fd_set rfds;
	int rc;

	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);

	while (finish == 0) {
		rc = select(fd + 1, &rfds, NULL, NULL, NULL);
		if (rc > 0) {
			memset(name, 0, sizeof(name)-1);
			ssize_t got = read(fd, name, sizeof(name)-1);
			if (got < 0) {
				perror("read");
				break;
			} else if (got == 0) {
				printf("EOF\\n");
				break;
			}
			else {
				/* remove trailing newline */
				if (name[strlen(name)-1] == '\n')
					name[strlen(name)-1] = '\0';
				XStoreName(dpy, root, name);
				while (XPending(dpy))
					XNextEvent(dpy, &ev);
			}
		}
	}
	/* will never get here */
	close(fd);
	XCloseDisplay(dpy);
	printf("Hello, I am the end of the main function.\n");

	exit(EXIT_SUCCESS);
}
Wie findet ihr das? Jetzt könnte ich doch das Programm mittels "kill -15 pid" aus dem Parent-Wrapper-Script heraus auf jeden Fall sauber beenden.

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 22.08.2018 21:34:26

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
22.08.2018 21:26:39

Code: Alles auswählen

...
			memset(name, 0, sizeof(name)-1);
			ssize_t got = read(fd, name, sizeof(name)-1);
...
Ich muss wohl doch noch mal schimpfen :mrgreen:

1) Wenn du read(...) nutzt dann

Code: Alles auswählen

memset(name, 0, sizeof(name));                       // KEIN -1 bei der Länge
ssize_t got = read(fd, name, sizeof(name)-1);    // EIN -1 bei der Länge wobei besser im Sinne von UTF-8 wäre:  sizeof(name) - sizeof(char)
2) Wenn du fgets(...) nutzt dann

Code: Alles auswählen

memset(name, 0, sizeof(name));                     // KEIN -1 bei der Länge
ssize_t got = fgets(name, sizeof(name),fp);    // KEIN -1 bei der Länge
fgets weiß ja, das du einen String in dem Puffer haben willst, also liest es MAX (num -1) Bytes aus dem Fifo uns hängt ein \0 hinten dran....

*WINKS*

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 22.08.2018 22:15:32

Ohje! :facepalm: :oops:
Vielen Dank für Deine Geduld, @bluestar! Manchmal muss ich einfach noch konzentrierter sein...

Ich habe es korrigiert (und dabei noch das sigaction an die richtige Stelle in main() gesetzt und den letzten comment entfernt, der nicht mehr gültig ist).

Code: Alles auswählen

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

static const char program_name[] = "test2xd";
static Display *dpy;
static int screen;
static Window root;
static XEvent ev;
static int finish;

static void
term_hdl (int signum)
{
	/* we have to use signum */
	switch (signum) {
		case SIGTERM:
			printf("Hello, I am the signal handler.\n");
			finish = 1;
	}
}

int
main(void) 
{
	char *display_name = NULL;
	char name[BUFSIZ];
	int fd;
	struct sigaction term_act;
	
	memset(&term_act, 0, sizeof(term_act));
	term_act.sa_handler = term_hdl;
	if (sigaction(SIGTERM, &term_act, NULL) < 0) {
		perror("sigaction");
		exit(EXIT_FAILURE);
	}

	dpy = XOpenDisplay(display_name);
	if (dpy == '\0') {
		fprintf(stderr, "%s:  unable to open display '%s'\n",
				program_name, XDisplayName (display_name));
		exit(2);
	}
	root = RootWindow(dpy, screen);

	/* fd = open("/tmp/dwmblocks_fifo_intern", O_RDWR | O_NONBLOCK); */
	fd = open("/home/user/fifo", O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		perror("open");
		exit(1);
	}

	fd_set rfds;
	int rc;

	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);

	while (finish == 0) {
		rc = select(fd + 1, &rfds, NULL, NULL, NULL);
		if (rc > 0) {
			memset(name, 0, sizeof(name));
			ssize_t got = read(fd, name, sizeof(name)-sizeof(char));
			if (got < 0) {
				perror("read");
				break;
			} else if (got == 0) {
				printf("EOF\\n");
				break;
			}
			else {
				/* remove trailing newline */
				if (name[strlen(name)-1] == '\n')
					name[strlen(name)-1] = '\0';
				XStoreName(dpy, root, name);
				while (XPending(dpy))
					XNextEvent(dpy, &ev);
			}
		}
	}
	close(fd);
	XCloseDisplay(dpy);
	printf("Hello, I am the end of the main function.\n");

	exit(EXIT_SUCCESS);
}
Was meint ihr übrigens zu dem neuen dpy-Test?
Also statt "if (!dpy)" "if (dpy == '\0')"? Ich habe noch auf der Suckless-Seite zusätzliche Links zu Coding-Style gesehen und in einem steht:
Don't use ‘!’ for tests unless it's a boolean [...]
Quelle: https://man.openbsd.org/style
Schien mir irgendwie Sinn zu ergeben.

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 22.08.2018 23:34:31

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
22.08.2018 22:15:32
Was meint ihr übrigens zu dem neuen dpy-Test?
Also statt "if (!dpy)" "if (dpy == '\0')"?
Das ist leider falscher. ;-)

XOpenDisplay(3) liefert einen Pointer zurueck und ...
Manpage XOpenDisplay(3) hat geschrieben: If XOpenDisplay does not succeed, it returns NULL.
Logisch korrekt ist also ``if (dpy == NULL)''. (Ich persoenlich finde ``if (!dpy)'' aber besser, weil mehr Signal und weniger Noise und damit direkter, treffender und lesbarer ... aber ich weiss, dass in dem Punkt viele da ``== NULL'' bevorzugen.)

In der Praxis laeuft's auf's gleiche hinaus, trotzdem sollte man NULL, 0 und '\0' voneinander unterscheiden und stets das Passende verwenden.

Ich habe noch auf der Suckless-Seite zusätzliche Links zu Coding-Style gesehen und in einem steht:
Don't use ‘!’ for tests unless it's a boolean [...]
Quelle: https://man.openbsd.org/style
Schien mir irgendwie Sinn zu ergeben.
Das hat durchaus seine Logik und einen Sinn. Ich selber verwende trotzdem `!' auch in anderen Situationen, immer wenn es mir so lesbarer erscheint. Bloss bei strcmp(3) verwende ich `!' nie und das aus starker Ueberzeugung: Es fuehrt einfach zu zu vielen Verstaendnisproblemen! Auch bei fork(2) halte ich `!' fuer nie sinnvoll. Insofern kann man schon sagen, dass man es nur fuer booleanartige Ausdruecke verwenden sollte. Es muss nicht nur 1 und 0 sein, es koennen schon auch viele verschiedene Werte zurueckgegeben werden (z.B. bei fopen(3)), aber wenn ich dort pruefe, ob der Aufruf fehlerhaft war, dann pruefe ich mit `!', weil ich etwas booleanartiges wissen will. Ich wuerde also sagen, dass man `!' nur verwenden sollte, wenn man eine booleanartige Pruefung macht (egal welcher Art die Daten sind).

Bei strcmp(3) macht das `!' IMO keinen Sinn, weil was soll ``if not string-compare'' bitte bedeuten? (``if not fopen'' macht dagegen viel Sinn.)

Bei fork(2) macht die Pruefung mit `!' auch keinen Sinn, weil ``!fork()'' bedeutet, dass man das Kind ist.

Entscheidend ist die Sinnhaftigkeit und das Verstaendnis beim Codeleser. Solange du dir nicht sicher bist, dass du gute Gruende hast, es auf eine bestimmte Art zu machen -- als Anfaenger hast du die fast nie --, dann mach es so wie die (guten) Styleguides es empfehlen.
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 23.08.2018 12:55:20

Oh, alles klar, vielen Dank, @meillo! Mit dem Unterschied zwischen Null, 0 und '\0' sollte ich mich wohl mal beschäftigen...

Eigentlich sollte man ja alle Variablen in der entsprechenden Funktion deklarieren, wenn sie nicht funktionsübergreifend genutzt werden, richtig?
Und nach den breaks des loops bzw. nach einem gescheiterten open() wäre ja auch Aufräumen angesagt...

Habe das mal versucht:

Code: Alles auswählen

#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <X11/Xlib.h>

/* BEGIN: user configuration */
static const char program_name[] = "test2xd";
/* static const char fifo[] = "/tmp/dwmblocks_fifo_intern"; */
static const char fifo[] = "/home/user/fifo";
/* END: user configuration */

static int finish;

static void
term_hdl (int signum)
{
	/* if we do not use signum, gcc complains */
	if (signum == SIGTERM)
		finish = 1;
}

int
main(void)
{
	char *display_name = NULL;
	char name[BUFSIZ];

	int fd;
	int rc;
	static int screen;
	int state = EXIT_SUCCESS;
	static Window root;

	static Display *dpy;
	struct sigaction term_act;
	static XEvent ev;

	fd_set rfds; /* fixed size buffer */
	
	memset(&term_act, 0, sizeof(term_act));
	term_act.sa_handler = term_hdl;
	if (sigaction(SIGTERM, &term_act, NULL) < 0) {
		perror("sigaction");
		exit(EXIT_FAILURE);
	}

	dpy = XOpenDisplay(display_name);
	if (dpy == NULL) {
		fprintf(stderr, "%s:  unable to open display '%s'\n",
				program_name, XDisplayName(display_name));
		exit(EXIT_FAILURE);
	}
	root = RootWindow(dpy, screen);

	fd = open(fifo, O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		perror("open");
		XCloseDisplay(dpy);
		exit(EXIT_FAILURE);
	}
	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);

	while (finish == 0) {
		rc = select(fd + 1, &rfds, NULL, NULL, NULL);
		if (rc > 0) {
			memset(name, 0, sizeof(name));
			ssize_t got = read(fd, name, sizeof(name)-sizeof(char));
			if (got < 0) {
				perror("read");
				state = EXIT_FAILURE;
				break;
			} else if (got == 0) {
				printf("EOF\\n");
				break;
			}
			else {
				/* remove trailing newline */
				if (name[strlen(name)-1] == '\n')
					name[strlen(name)-1] = '\0';
				XStoreName(dpy, root, name);
				while (XPending(dpy))
					XNextEvent(dpy, &ev);
			}
		}
	}
	close(fd);
	XCloseDisplay(dpy);
	exit(state);
}

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 23.08.2018 13:43:38

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 12:55:20
Mit dem Unterschied zwischen Null, 0 und '\0' sollte ich mich wohl mal beschäftigen...
Die Binaerdarstellung all dieser Werte sind nur Nullen. (Auch wenn der Standard das nicht erfordert, so ist es in der Realitaet in allen Implementierungen so und es macht auch Sinn.)

Die Unterschiede liegen nur in den Datentypen. Finde heraus, welche Datentypen die drei Ausdruecke haben! 0 und '\0' sind einfach, NULL ist schwieriger.



Und ja, globale Variablen sollte man moeglichst vermeiden.
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 23.08.2018 13:52:27

Alles klar, mache ich.
Was mir übrigens noch eingefallen ist (habe ich oben vergessen): Ich kann mir beim schließen des Prozesses das "-15" als Argument für kill sparen, denn das default-Signal von kill ist eh SIGTERM:

man 1 kill
The default signal for kill is TERM.

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 23.08.2018 16:38:27

Ich bin mir nicht sicher allerdings will ich es dennoch mal loswerden:

Code: Alles auswählen

while (XPending(dpy))
	XNextEvent(dpy, &ev);
Der Code wird bei dir ja nur nach dem Empfang von Daten ausgeführt, mein Gefühl sagt mir das du hier auf kurz oder lang ein Problem bekommen wirst, wenn der EventPuffer von X voll läuft, während du "unendlich lange" auf Daten aus deinem Fifo wartest.

Ich würde die While Schleife (stark vereinfacht so gestalten):

Code: Alles auswählen

while (finished==0) {
	while (XPending(dpy)) {
		XNextEvent(dpy, &ev);
	}
	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);
	rc = select (.....) // mit timeout von 1 sek oder 5 sek ... 
	if (rc > 0) {
		...
	}
}

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 23.08.2018 16:50:25

Und noch ne Anmerkung möchte ich gern loswerden:

Code: Alles auswählen

if (name[strlen(name)-1] == '\n')
	name[strlen(name)-1] = '\0';
Wenn strlen(name) gleich 0 ist, dann schreibst du an name[-1] = '\0' in den Speicher....

Wäre also wohl besser so:

Code: Alles auswählen

if ( (strlen(name) > 0) && (name[strlen(name)-1] == '\n') )
	name[strlen(name)-1] = '\0';

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 23.08.2018 17:44:00

bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 16:50:25
Und noch ne Anmerkung möchte ich gern loswerden:

Code: Alles auswählen

if (name[strlen(name)-1] == '\n')
	name[strlen(name)-1] = '\0';
Wenn strlen(name) gleich 0 ist, dann schreibst du an name[-1] = '\0' in den Speicher....
Gut gesehen! ... aber kann das im konkreten Fall denn passieren? Schliesslich sind wir im Else-Teil, wo immer Inhalt da sein sollte.

Ist es nicht so: Wenn wir nichts lesen ist's EOF, und wenn wir etwas lesen, dann haben wir mindestens ein Newline?

Wäre also wohl besser so:

Code: Alles auswählen

if ( (strlen(name) > 0) && (name[strlen(name)-1] == '\n') )
	name[strlen(name)-1] = '\0';
Oder:

Code: Alles auswählen

if (strlen(name) == 0) {
        continue;
}
if (name[strlen(name)-1] == '\n') {
	name[strlen(name)-1] = '\0';
}
... wobei manch ein C-Programmierer natuerlich ``if (!*name) {'' schreiben wuerde. ;-)


Fehlerfaelle nacheinander auszusortieren und am Ende die eigentliche Verarbeitung zu machen, fuehrt meist zu einfacher verstaendlicherem Code mit weniger Fehlern.

Btw: Wenn dein C-Code mal nicht mehr sinnvoll mit 8-Zeichen-Tabs eingerueckt auf 80-Zeichen-lange Zeilen passt, dann ist das ein Zeichen dafuer, dass er stilistisch verbessert werden kann. (Bei anderen Programmiersprachen sieht das teilweise anders aus, aber bei C trifft die Regel gut zu.)
Use ed once in a while!

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 23.08.2018 18:45:50

Meillo hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 17:44:00
Ist es nicht so: Wenn wir nichts lesen ist's EOF, und wenn wir etwas lesen, dann haben wir mindestens ein Newline?
Wenn wir exakt ein '\0' lesen, dann ist got = 1 und strlen(name) = 0.

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 23.08.2018 18:49:18

bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 18:45:50
Meillo hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 17:44:00
Ist es nicht so: Wenn wir nichts lesen ist's EOF, und wenn wir etwas lesen, dann haben wir mindestens ein Newline?
Wenn wir exakt ein '\0' lesen, dann ist got = 1 und strlen(name) = 0.
Okay, wenn jemand Binaerdaten reinschreibt. Akzeptiert!
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 23.08.2018 21:15:54

Vielen Dank für eure weiteren Anregungen! :THX:

Wegen dem X-Code werde ich nochmal recherchieren. Man muss da bei Manuals und so aufpassen, denn im Gegensatz zu einem Window-Manager wartet mein Programm ja nicht auf einen Xevent (also z.B. einen Klick vom User), sondern der Xserver wartet auf mein Programm. Da scheint mir das Abarbeiten des Event-Buffers am Ende des loops irgendwie logischer?

Den Test auf newline werde ich anpassen, danke!
Meillo hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 17:44:00
... wobei manch ein C-Programmierer natuerlich ``if (!*name) {'' schreiben wuerde. ;-)
Ja, das würde ich auch versuchen, denn dann hätte man einen function-Aufruf weniger. Aber man hat mir gesagt, ich solle doch im Zweifelsfall lieber die Lesbarkeit als die Performance-Optimierung wählen... :mrgreen:


Den Unterschied zwischen 0, NULL und ’\0’ habe ich jetzt übrigens glaube ich schon besser verstanden:
- 0 ist ein integer
- NULL ist ein pointer, d.h., dass NULL auf eine Speicher-Adresse zeigt, deren Wert 0 ist. Zugleich ist NULL als Makro des Compilers definiert.
- ’\0’ ist ein Zeichen, das nichts enthält. Also ein byte, dessen bits alle 0 sind.
Richtig so?
So wie ich es verstehe, kann man 0 immer für ifs nutzen, denn NULL und ’\0‘ laufen auch auf den int 0 hinaus.

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 23.08.2018 21:21:17

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 21:15:54
Da scheint mir das Abarbeiten des Event-Buffers am Ende des loops irgendwie logischer?
Das Problem beseht ja darin, dass du im worst case den Event Buffer nie abarbeitest.... So lange über deinen Fifo keine Daten reinkommen, bleibt das select(....) ja blockierend stehen... Ergo stauen sich die Events auf.

Was mir auch noch aufgefallen ist, dass du zwei Commands hintereinander auch auf einen Schlag einlesen könntest. In dem Falle musst du über deinen Empfangspuffer iterieren und alle Commands nacheinander abarbeiten. strchr(...) dürfte dir da weiterhelfen.

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 23.08.2018 22:00:27

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 21:15:54
Meillo hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 17:44:00
... wobei manch ein C-Programmierer natuerlich ``if (!*name) {'' schreiben wuerde. ;-)
Ja, das würde ich auch versuchen, denn dann hätte man einen function-Aufruf weniger. Aber man hat mir gesagt, ich solle doch im Zweifelsfall lieber die Lesbarkeit als die Performance-Optimierung wählen... :mrgreen:
Ich habe das ja gerade wegen der Lesbarkeit vorgeschlagen! :-D

Die Lesbarkeit ist allerdings nur dann besser, wenn man das Idiom kennt und gewoehnt ist. Idiome sind ein wichtiges Hilfsmittel fuer gute Lesbarkeit: Man hat also Codestuecke, die immer gleich sind, so dass man sie gar nicht genau anschauen muss, sondern auf den ersten Blick weiss, was sie bedeuten. Ein typisches Idiom sind Zaehlschleifen in C, die man immer als ``for (i=0; i<num; i++) {'' schreiben sollte. Notfalls kann man auch noch ``for (i=1; i<=num; i++) {'' verwenden. Andere Faelle sollte man vermeiden. Wenn Code so geschrieben ist, dann muss ich mir keine Zaehlschleife mehr genau anschauen, weil ich auf den ersten Blick weiss, wie oft sie laufen wird.

Man entwickelt dann auch einen Blick dafuer, wenn sie ``komisch'' aussehen, d.h. also nicht so sind wie erwartet. Das sind aber Faelle, wo Kommentare sehr hilfreich sind: Immer dann wenn etwas nicht so ist wie man es erwarten wuerde. Codelesen hat viel mit Aufmerksamkeit zu tun. Man muss die Aufmerksamkeit des Codelesers an die relevanten Stellen lenken, weil er nur ein gewisses Mass an Aufmerksamkeit hat, das man moeglichst sinnvoll verteilen sollte. Das jedenfalls sind einige Meiner Gedanken zu gutem Code ... :-)


Den Unterschied zwischen 0, NULL und ’\0’ habe ich jetzt übrigens glaube ich schon besser verstanden:
- 0 ist ein integer
- NULL ist ein pointer, d.h., dass NULL auf eine Speicher-Adresse zeigt, deren Wert 0 ist. Zugleich ist NULL als Makro des Compilers definiert.
- ’\0’ ist ein Zeichen, das nichts enthält. Also ein byte, dessen bits alle 0 sind.
Richtig so?
Ja.

0 war einfach.

'\0' ist ein `char', und enthaelt das Zeichen ASCII NUL, also das allererste (an nullter Position). Es ist korrekt, dass bei ihm alle Bits null sind.

NULL ist ein Pointer. Ein Pointer hat als Wert eine Speicheradresse. NULL hat als Wert die Adresse 0. An der Stelle sind kein Code und keine Daten zu finden. Bei virtueller Speicherverwaltung ist die nullte Page dem Prozess nicht zugeordnet. Wenn man den NULL-Pointer dereferenziert, dann erzeugt das einen Segfault. Darum sollte man auch ganz brav immer erst auf NULL pruefen, bevor man Rueckgabewerte dereferenziert. ;-)

Definiert ist NULL hier: /usr/include/linux/stddef.h

Code: Alles auswählen

#define NULL ((void *)0)
Frueher (bevor es void* gab), war NULL ein char*. Letztlich muss es halt ein Pointer sein, der sich in jeden anderen casten laesst.
So wie ich es verstehe, kann man 0 immer für ifs nutzen, denn NULL und ’\0‘ laufen auch auf den int 0 hinaus.
Ja, das wird dann automatisch gecastet. Dabei geht alles glatt weil alle drei Werte (in allen Implementierungen) jeweils nur Nullen in allen Bits haben.

Wenn du aber explizit vergleichst, solltest du moeglichst die richtigen Typen verwenden, weil das IMO Irritationen vermeidet. Gerade als Anfaenger ist es in C wichtig stets auf die Datentypen zu achten. Zumeist sind es, meiner Erfahrung nach, eher die Anfaenger, die ueberall 0 schreiben wollen (damit sie sich das Nachdenken sparen koennen), als dass die Profis sich damit ein paar Zeichen sparen wollen. Sich aber bei C das Nachdenken und das komplette Verstehen sparen zu wollen, holt einen sehr schnell ein! C ist ein scharfes Messer -- man sollte besser genau wissen was man damit tut!


Btw: Der obere und der untere Teil meiner Mail scheinen sich zu widersprechen. Teilweise tun sie das ... und zeigen damit, dass es stets um das Abwaegen verschiedener Ziele geht. Es gibt nur wenige einfache Antworten (wie, auch bei einzeiligen Bloecken immer geschweifte Klammern zu setzen ;-) ); fast immer geht es um das Verstehen der komplexen Zusammenhaenge und das Treffen begruendbarer und stimmiger Entscheidungen.
Use ed once in a while!

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 23.08.2018 22:04:43

bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 21:21:17
Was mir auch noch aufgefallen ist, dass du zwei Commands hintereinander auch auf einen Schlag einlesen könntest. In dem Falle musst du über deinen Empfangspuffer iterieren und alle Commands nacheinander abarbeiten. strchr(...) dürfte dir da weiterhelfen.
... so findet man bei Low-Level-Loesungen oft noch diesen und jenen unbedachten Randfall.

Dieser Fall kann bei der fgets(3)-Loesung nicht vorkommen. ;-)
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 23.08.2018 23:14:02

Meillo hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 22:00:27
Sich aber bei C das Nachdenken und das komplette Verstehen sparen zu wollen, holt einen sehr schnell ein!
Ja, das sehe ich ein. Ich habe auch den Text über 0, NULL und '\0' nicht von irgendwo aus dem Internet per Copy&Paste in meinen Beitrag eingefügt, sondern habe den Inhalt nach dem Recherchieren und Verstehen selbstständig geschrieben. Ich hatte nur Teile meines Beitrages im Schreibprogramm verfasst, nicht in der Textbox des Forums, deshalb sind die Quotes um \0 andere als sonst (nicht, dass Du Falsches vermutest).
bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 21:21:17
Was mir auch noch aufgefallen ist, dass du zwei Commands hintereinander auch auf einen Schlag einlesen könntest. In dem Falle musst du über deinen Empfangspuffer iterieren und alle Commands nacheinander abarbeiten.
Meinst Du, dass ich das als feature integrieren könnte oder ist das ein bug? Wenn letzteres, wieso kann es dazu kommen? Es ist doch so, dass nach einem write die Schleife einmal durchläuft und beim nächsten write dann wieder. Wie kann read da zwei Zeilen - also zwei writes - auf einmal einlesen?

ad btw (Latein-Englisch-Misch-Slang 8) ):
Ich könnte mir geschweifte Klammern um einzeilige Blöcke vorstellen, doch in der Praxis mache ich es dann doch immer anders (weil es auch im Suckless-Code - soweit ich gesehen habe - so gemacht wird).
Die C-for-loops kenne ich glücklicherweise schon (ziemlich ähnlich) von bash: "for ((i=1; i<=5; i++)); do echo $i; done". Wusste aber noch nicht, dass man auch "i<number" schreiben kann. In bash wäre das auch möglich (habe es gerade probiert): "for ((i=1; i<5; i++)); do echo $i; done". Gibt dann eben 1-4 aus. Somit muss man 6 schreiben, wenn man 5 will. Da ich an die erste Variante gewöhnt bin, fand ich das erstmal nicht so leserlich, ist aber im Prinzip ja egal...

So, genug für heute, die X-Problematik, die @bluestar angesprochen hat, nehme ich morgen nochmal in Angriff...

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 23.08.2018 23:22:43

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 23:14:02
bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 21:21:17
Was mir auch noch aufgefallen ist, dass du zwei Commands hintereinander auch auf einen Schlag einlesen könntest. In dem Falle musst du über deinen Empfangspuffer iterieren und alle Commands nacheinander abarbeiten.
Meinst Du, dass ich das als feature integrieren könnte oder ist das ein bug? Wenn letzteres, wieso kann es dazu kommen? Es ist doch so, dass nach einem write die Schleife einmal durchläuft und beim nächsten write dann wieder. Wie kann read da zwei Zeilen - also zwei writes - auf einmal einlesen?
Ich würde das als Bug bezeichnen, das Problem entsteht halt, wenn du in einem Write "name1\nname2\n" sendest.
Zum Testen kannst du ja mal auf der Konsole folgendes eingeben:

Code: Alles auswählen

echo -e "name1\nname2">/home/user/fifo

Benutzeravatar
schorsch_76
Beiträge: 2535
Registriert: 06.11.2007 16:00:42
Lizenz eigener Beiträge: MIT Lizenz

Re: C: fifo lesen

Beitrag von schorsch_76 » 24.08.2018 07:33:18

bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 23:22:43
Ich würde das als Bug bezeichnen, das Problem entsteht halt, wenn du in einem Write "name1\nname2\n" sendest.
Zum Testen kannst du ja mal auf der Konsole folgendes eingeben:

Code: Alles auswählen

echo -e "name1\nname2">/home/user/fifo
Jop, das ist ein Bug. Das habe ich eben gemeint mit "Die Bytes in einen Parser schicken, consumend_bytes und den Status des Requests" zurück geben.

Wenn du das mit "name1\nname2" rein schickts ist das ein atomic write von 2 Requests. (Fifo und pipe schreiben atomic wenn die Größe kleiner als PIPE_BUF ist). Das ist auch so in POSIX spezifziert. PIPE_BUF ist auf amd64 Linux 64k.

[1] https://www.gnu.org/software/libc/manua ... -and-FIFOs

EDIT: Hier ist so ein Beispiel in Boost wie so was geht.
[2] https://www.boost.org/doc/libs/1_68_0/d ... parser.hpp
[3] https://www.boost.org/doc/libs/1_68_0/d ... parser.cpp

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 24.08.2018 08:56:44

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 23:14:02
Meillo hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 22:00:27
Sich aber bei C das Nachdenken und das komplette Verstehen sparen zu wollen, holt einen sehr schnell ein!
Ja, das sehe ich ein. Ich habe auch den Text über 0, NULL und '\0' nicht von irgendwo aus dem Internet per Copy&Paste in meinen Beitrag eingefügt, sondern habe den Inhalt nach dem Recherchieren und Verstehen selbstständig geschrieben. Ich hatte nur Teile meines Beitrages im Schreibprogramm verfasst, nicht in der Textbox des Forums, deshalb sind die Quotes um \0 andere als sonst (nicht, dass Du Falsches vermutest).
Keine Sorge, ich hatte nichts Falsches vermutet. ;-)

ad btw (Latein-Englisch-Misch-Slang 8) ):
Ich könnte mir geschweifte Klammern um einzeilige Blöcke vorstellen, doch in der Praxis mache ich es dann doch immer anders (weil es auch im Suckless-Code - soweit ich gesehen habe - so gemacht wird).
Auch im K&R fehlen die Klammern. In Go haben die gleichen Sprachdesigner sie aber erzwungen ... weil es das bessere Design ist. Wenn du in einen Ein-Zeilen-Block ohne Klammern eine weitere Zeile hinzufuegen willst, musst du drei Zeilen veraendern. Falls du vergisst die geschweiften Klammern zu ergaenzen hast du einen Bug. Das lohnt sich IMO nicht in Kauf zu nehmen ... nur weil man sich zwei Zeichen zu tippen und eine weitere Zeile im Code sparen will. Fuer mich ist die Antwort auf diesen Kompromiss sehr eindeutig. Fuer mehr Infos kannst du auch ueber den Unterschied zwischen den Programmierstilen K&R vs. 1TBS nachlesen.

Die C-for-loops kenne ich glücklicherweise schon (ziemlich ähnlich) von bash: "for ((i=1; i<=5; i++)); do echo $i; done". Wusste aber noch nicht, dass man auch "i<number" schreiben kann. In bash wäre das auch möglich (habe es gerade probiert): "for ((i=1; i<5; i++)); do echo $i; done". Gibt dann eben 1-4 aus. Somit muss man 6 schreiben, wenn man 5 will. Da ich an die erste Variante gewöhnt bin, fand ich das erstmal nicht so leserlich, ist aber im Prinzip ja egal...
Da hast du etwas Entscheidendes an meiner Aussage noch nicht verstanden. Ich habe genau zwei Formen aufgefuehrt. Die erste beginnt bei 0 und vergleicht mit <. Die zweite beginnt bei 1 und vergleicht mit <=. Nur genau diese zwei sollten verwendet werden, keine anderen. Damit eben genau das, was du hier beschreibst, nicht passiert. Die Zahl beim Vergleich muss immer der Anzahl der Schleifendurchlaeufe entsprechen! Darum eben genau diese zwei Formen und keine anderen!

Warum der Start mit 0 besser ist als der mit 1, darueber hat Dijkstra geschrieben: http://www.cs.utexas.edu/users/EWD/tran ... WD831.html
Use ed once in a while!

Benutzeravatar
Meillo
Moderator
Beiträge: 8781
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: fifo lesen

Beitrag von Meillo » 24.08.2018 09:01:33

bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 23:22:43
RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 23:14:02
bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 21:21:17
Was mir auch noch aufgefallen ist, dass du zwei Commands hintereinander auch auf einen Schlag einlesen könntest. In dem Falle musst du über deinen Empfangspuffer iterieren und alle Commands nacheinander abarbeiten.
Meinst Du, dass ich das als feature integrieren könnte oder ist das ein bug? Wenn letzteres, wieso kann es dazu kommen? Es ist doch so, dass nach einem write die Schleife einmal durchläuft und beim nächsten write dann wieder. Wie kann read da zwei Zeilen - also zwei writes - auf einmal einlesen?
Ich würde das als Bug bezeichnen, das Problem entsteht halt, wenn du in einem Write "name1\nname2\n" sendest.
Oder wenn dein FIFO-Leseprogramm eine Weile schlaeft und derweil mehrere verschiedene Writes in die FIFO gemacht werden.

Vgl: viewtopic.php?f=34&t=170569&start=15#p1182116
(In der Mitte des Posts meine Reaktion auf den Einwand von schorsch_76.)
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: C: fifo lesen

Beitrag von RobertDebiannutzer » 24.08.2018 11:12:18

Bezüglich der for-loops: Alles klar. Mit Null anfangen kann auch in bash sinnvoll sein, wenn man nämlich z.B. die loop-Variable auch als index für arrays nutzen will (welche ja bekanntlich bei 0 anfangen). Beispiel: "arr=(eins zwei drei vier fünf) && for ((i=0; i<5; i++)); do echo ${arr[$i]}; done" (Normalerweise macht man ja in bash keinen Loop, um einen array nach stdout zu schreiben, soll nur ein einfaches Beispiel sein.)

Zurück zu C:
Wenn ich es richtig verstehe, hätte ich den bug mit read nicht, wenn ich fgets nutzen würde? Dann sollte ich das vielleicht doch machen und mir mein Beispiel mit read aufheben für Anwendungszwecke, bei denen ich mehrere Zeilen aus der fifo auf einmal lesen möchte.

Dann würde das Programm so aussehen?

Code: Alles auswählen

#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <X11/Xlib.h>

/* BEGIN: user configuration */
static const char program_name[] = "myxd";
/* static const char fifo[] = "/tmp/dwmblocks_fifo_intern"; */
static const char fifo[] = "/home/user/fifo";
/* END: user configuration */

static int finish;

static void
term_hdl (int signum)
{
	/* if we do not use signum, gcc complains */
	if (signum == SIGTERM) {
		finish = 1;
	}
}

int
main(void)
{
	static char *display_name = NULL;
	static char name[BUFSIZ];

	static FILE *in;

	static int screen;
	static int state = EXIT_SUCCESS;
	static Window root;

	static Display *dpy;
	static struct sigaction term_act;
	static XEvent ev;

	memset(&term_act, 0, sizeof(term_act));
	term_act.sa_handler = term_hdl;
	if (sigaction(SIGTERM, &term_act, NULL) < 0) {
		perror("sigaction");
		exit(EXIT_FAILURE);
	}

	dpy = XOpenDisplay(display_name);
	if (dpy == NULL) {
		fprintf(stderr, "%s:  unable to open display '%s'\n",
				program_name, XDisplayName(display_name));
		exit(EXIT_FAILURE);
	}
	root = RootWindow(dpy, screen);

	in = fopen(fifo, "r+");
	if (in == NULL) {
		perror("fopen");
		XCloseDisplay(dpy);
		exit(EXIT_FAILURE);
	}

	while (finish == 0) {
			while (XPending(dpy)) {
				XNextEvent(dpy, &ev);
			}
			memset(name, 0, sizeof(name));
			if (fgets(name, sizeof(name), in) == NULL) {
				/* perror("fgets");
				state = EXIT_FAILURE; */
				break;
			} else {
				/* remove trailing newline */
				if ((*name) && (name[strlen(name)-1] == '\n')) {
					name[strlen(name)-1] = '\0';
				}
				XStoreName(dpy, root, name);
			}
	}
	fclose(in);
	XCloseDisplay(dpy);
	exit(state);
}
Rückgabewert von sigaction is int, wird gegen 0 verglichen
"dpy" ist pointer, wird gegen NULL vergleichen
"in" ist pointer, wird gegen NULL vergleichen
"finish" ist int, wird gegen 0 verglichen
Rückgabewert von XPending ist int, wird von while wie boolean behandelt (ich habe das jetzt erstmal gelassen, weil ich mit den Überlegungen zum X Code noch nicht fertig bin)
Rückgabewert von fgets ist im Fehlerfall NULL:
Manpage von fgets hat geschrieben:fgets() returns s on success, and NULL on error or when end of file occurs while no characters have been read.
Leider beklagt sich fgets bei kill über "interrupted system call", wenn ich die perror-Zeile nicht kommentiere.

Und bei fgets kein "sizeof(name)-1", ich habe dran gedacht!

@meillo: Deine Anmerkung zu den geschweiften Klammern finde ich sehr sinnvoll. Ich habe das schon in patches gesehen, wie dann die if-/for-/while-Zeilen mit getauscht werden müssen, weil die Klammer angehängt werden muss. Für den Patch-Leser ist das nicht so angenehm...
Übrigens: Die geschweifte Klammer in meinem letzten if ist gerade das 80. Zeichen in der Zeile. :mrgreen:

Benutzeravatar
bluestar
Beiträge: 2334
Registriert: 26.10.2004 11:16:34
Wohnort: Rhein-Main-Gebiet

Re: C: fifo lesen

Beitrag von bluestar » 24.08.2018 11:18:03

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
24.08.2018 11:12:18

Dann würde das Programm so aussehen?

Code: Alles auswählen

...
	while (finish == 0) {
			while (XPending(dpy)) {
				XNextEvent(dpy, &ev);
			}
			memset(name, 0, sizeof(name));
			if (fgets(name, sizeof(name), in) == NULL) {
...
Da fgets ja endlos blockiert (also bis Daten im Fifo anliegen) hast du das Problem mit dem XEvent damit nicht gelöst, sie meinen Post wo ich für das Select ein Timeout von 1 bzw. 5 Sekunden vorschlage:
bluestar hat geschrieben: ↑ zum Beitrag ↑
23.08.2018 16:38:27

Code: Alles auswählen

while (finished==0) {
	while (XPending(dpy)) {
		XNextEvent(dpy, &ev);
	}
	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);
	rc = select (.....) // mit timeout von 1 sek oder 5 sek ... 
	if (rc > 0) {
		...
	}
}

Antworten