Konkatinierung von char* - gängige Praxis



  • Hallo,

    ich bin sehr erfahren in C und würde daher gerne eure Meinung über das Codesnippet unten wissen. Es tut, was ich will, aber ich weiß nicht, ob man es nicht eleganter hätte machen können oder ob ich Unnötiges tue.

    Ich möchte den ersten Parameter der Binary an den Pfad des Verzeichnisses hängen, in welchem das Binary liegt.

    // remove everything after las slash
    // ./usr/local/bin/my_bin --> ./usr/local/bin
    char* remove_bin_name(const char* name) {
    
    	size_t len = strlen(name);
    
    	int i=len-1;
    	while(name[i] != '/')
    		i--;
    
    	char* path = (char*) malloc(sizeof(char) * i);	// stored on the heap
    
    	for(; i>=0; i--)	
    		path[i] = name[i];
    
    	return path; // const char* is not stored on the heap. What is happening?
    }
    
    int main(int argc, char *argv[]) {
    
    	if(argc != 2) {
    		printf("Check your arguments!\n");
    		return 0;
    	}
    
    	char* base_path = remove_bin_name(argv[0]);
    	size_t len_bp = strlen(base_path);
    	size_t len_map_file = strlen(argv[1]);	
    
            // concat base path and first argument
    	char* file = (char*) malloc(sizeof(char) * len_bp+len_map_file);
    	strncpy(file, base_path, len_bp);
    	strncat(file, argv[1], len_map_file);	free(base_path);
    
    	printf("%s\n", file);
    
       return 0;
    }
    

    1. Geht's besser?

    2. In remove_bin_name leget ich ein ein char-Array auf den Heap. Wenn ich nun den Rückgabetypen in const char* ändere, was genau passiert dann? const char*'s liegen doch im Codesegment und nicht auf dem Heap? Wird das Array dann automatisch kopiert und der Heapspeicher freigeben? Ich vermute mal nicht...



  • Ich glaub, Du hast da was missverstanden: const char* können überall liegen und auch überall hin zeigen. Auf dem Stack, auf dem Heap, in .text, etc. const char* heisst nur, dass Du die Zeichen, auf den der Zeiger zeigt, über diesen Zeiger nicht verändern darfst.



  • [quote="pointerbub"... Es tut, was ich will, ...[/quote]

    Das ist rein zufällig, denn deine mit malloc() erzeugten Buffer sind in beiden Fällen zu klein (vielleicht auch gar nicht vorhanden, da keine Prüfung des Returncodes), für die Null-Terminierung des Buffers aus der Hilfsfunktion remove_bin_name() ist außerdem nicht gesorgt.

    Es wäre ausreichend, in der Hilfsfunktion nur die benötigte Länge des Strings zu ermitteln und auf den Buffer zu verzichten, denn der Teilstring liegt ja bereits über argv[0] vor. Dabei sollte auch in geeigneter Weise reagiert werden, falls argv[0] unerwartet kein '/' enthält, so z. B. auch bei einem Leerstring.

    In main() könnten ebenfalls malloc() und das (fehlende) zugehörige free() entfallen, wenn ein Buffer in der geeigneten Größe für die max. mögliche Pfad-/Dateilänge vorgehalten würde.



  • Zufall schrieb:

    In main() könnten ebenfalls malloc() und das (fehlende) zugehörige free() entfallen, wenn ein Buffer in der geeigneten Größe für die max. mögliche Pfad-/Dateilänge vorgehalten würde.

    Das entbindet dich allerdings nicht von der Pflicht zur Prüfung, ob die beiden Teilstrings inkl. Nullterminierung tatsächlich in diesen Buffer passen.



  • pointerbub schrieb:

    ich bin sehr erfahren in C

    Wow.
    Zeugt vom gesundem Selbstbewusstsein, näher betrachtet aber eher eine naive Sicht der Dinge bzw. eine pubertäre Provokation um professionelle Antworten zu bekommen.

    - nur Deppen benutzen strncpy
    - es ist nicht sichergestellt, dass argv[0] den Programmnamen enthält
    - '/' als Verzeichnisseparator ist ebenfalls nicht garantiert
    - malloc casten nur Deppen
    - free vergessen
    - in C werden nirgendwo Arrays 'automatisch kopiert'

    char *replacefilename(const char **argv,char *s)
    {
    	char *p = strrchr(*argv,'/');
    	if(!p) p = strrchr(*argv,'\\');
    	if(!p) return 0;
    	*s=0;
    	strncat(s,*argv,p-*argv+1);
    	return strcat(s,argv[1]);
    }
    
    int main(void) {
    	const char *a[]={"/foo/bar","baz"}; void*p;
    	puts(replacefilename(a,p=malloc(1+strlen(a[0])+strlen(a[1]))));
    	free(p);
    	return 0;
    }
    

    http://ideone.com/F3PyM5



  • Wutz schrieb:

    pointerbub schrieb:

    ich bin sehr erfahren in C

    Wow.
    Zeugt vom gesundem Selbstbewusstsein, näher betrachtet aber eher eine naive Sicht der Dinge bzw. eine pubertäre Provokation um professionelle Antworten zu bekommen.

    bla bla. Da fehlt ein "nicht".

    Zufall schrieb:

    pointerbub schrieb:

    Es tut, was ich will, ...

    Das ist rein zufällig, denn deine mit malloc() erzeugten Buffer sind in beiden Fällen zu klein.

    Du meinst, weil ich das '\0' nicht berücksichtige? Ok, das sollte ich ändern.

    Zufall schrieb:

    In main() könnten ebenfalls malloc() und das (fehlende) zugehörige free() entfallen, wenn ein Buffer in der geeigneten Größe für die max. mögliche Pfad-/Dateilänge vorgehalten würde.

    Warum? Das würde bedeuten, dass ich statisch eine Arraygröße vorgeben muss, obwohl ich nicht weiß, wie groß die Daten wirklich sein werden. Ich möchte einen Buffer Overflow verhindern, will aber auch nicht mehr Daten reservieren als nötig.

    [quote="Wutz"]

    pointerbub schrieb:

    - nur Deppen benutzen strncpy

    Begründung? Deppen nutzen strcpy.

    [quote="Wutz"]

    pointerbub schrieb:

    - es ist nicht sichergestellt, dass argv[0] den Programmnamen enthält

    Wann ist das nicht der Fall?

    [quote="Wutz"]

    pointerbub schrieb:

    - malloc casten nur Deppen

    Begründung? Wie allokiert Du denn dynamisch Speicher? Castet Du später void Pointer?



  • pointerbub schrieb:

    Wutz schrieb:

    - malloc casten nur Deppen

    Begründung? Wie allokiert Du denn dynamisch Speicher? Castet Du später void Pointer?

    void* ist implizit in jeden anderen Zeiger-Typ konvertierbar. Der Cast ist also unnötig. Wenn Dein Compiler ohne den Cast nicht kompilieren will, ist er kaputt.



  • pointerbub schrieb:

    Wutz schrieb:

    - nur Deppen benutzen strncpy

    Begründung? Deppen nutzen strcpy.

    Stop using strncpy already!

    pointerbub schrieb:

    Wutz schrieb:

    - es ist nicht sichergestellt, dass argv[0] den Programmnamen enthält

    Wann ist das nicht der Fall?

    Immer dann, wenns dem Host Environment anders einfällt. Garantiert? Nie. Geh Standard lesen.


  • Mod

    Swordfish schrieb:

    pointerbub schrieb:

    Wutz schrieb:

    - es ist nicht sichergestellt, dass argv[0] den Programmnamen enthält

    Wann ist das nicht der Fall?

    Immer dann, wenns dem Host Environment anders einfällt. Garantiert? Nie. Geh Standard lesen.

    Wobei es schon ziemlich spitzfindig ist zu sagen, dass da nicht zuverlässig der Programmaufruf stünde. Ob man für Windows, Linux, Mac oder sonst ein Betriebssystem mit > 0.1 Promille Marktanteil programmiert weiß man schließlich in der Regel. Und dann ist dieses Verhalten auch garantiert. Der eigentliche Knackpunkt ist, dass ich oben nicht ohne Grund "Programmaufruf" geschrieben habe. Der Threadersteller geht aber davon aus, dass da garantiert der Name der Executable mit vollem Pfad stünde. Dies ist definitiv nicht garantiert. Einfache Gegenbeispiele sind Verknüpfungen oder Aliase. Denn in argv[0] steht wirklich der Bezeichner über den das Programm gestartet wurde, dieser ist aber nicht unbedingt der Dateiname¹ der Executable.

    ¹: Es ist auch nicht unbedingt gegeben, dass eine Executable überhaupt einen eindeutigen Dateinamen haben muss (Hardlinks...) oder ob es überhaupt eine Executable-Datei gibt (Builtins...).



  • Swordfish schrieb:

    Stop using strncpy already!

    Das funktioniert aber nur in C++.



  • SG1 schrieb:

    void* ist implizit in jeden anderen Zeiger-Typ konvertierbar. Der Cast ist also unnötig. Wenn Dein Compiler ohne den Cast nicht kompilieren will, ist er kaputt.

    Das wusste ich nicht. Danke für den Hinweis.

    Swordfish schrieb:

    Stop using strncpy already!

    Sehr interessanter Artikel. Dass strcpy anfällig gegenüber Buffer-Overflows ist, war mir klar. Daher habe ich auf das "n" im Namen geachtet. Dass diese Funktionen aber keine Nullterminierung garantieren, war mir nicht klar und mir leuchtet die potenzielle Gefahr ein. Die vorgeschlagene Lösung kommt für mich nicht partiell infrage, weil ich, wie SG1 richtig festgestellt habe, nicht C++ programmiere, sondern ausschließlich C. Vermutlich bleibt mir nicht anderes übrig, als die Funktion selbst zu implementieren.



  • Kann mir jemand sagen, warum im Code von Wutz

    p-*arg+1
    

    16 ergibt? p ist der Pointer auf die Stelle, an der das letzte / steht. *arg ist der erste const char* in a. Wie funktioniert die Zeigerarithmetik hier?



  • *argv zeigt auf den programmpfad, p auf den gefundenen pfadseparator, somit ist p - *argv die distanz der beiden, die anzahl der zeichen bis zum pfadseparator. Damit dieser eingeschlossen wird, noch eins dazuaddiert.



  • pointerbub schrieb:

    Die vorgeschlagene Lösung kommt für mich nicht partiell infrage, weil ich, wie SG1 richtig festgestellt habe, nicht C++ programmiere, sondern ausschließlich C.

    Ich verstehe in diesem Satz das Wort "partiell" nicht. Was sagt es aus?



  • volkard schrieb:

    pointerbub schrieb:

    Die vorgeschlagene Lösung kommt für mich nicht partiell infrage, weil ich, wie SG1 richtig festgestellt habe, nicht C++ programmiere, sondern ausschließlich C.

    Ich verstehe in diesem Satz das Wort "partiell" nicht. Was sagt es aus?

    Das "nicht" ist dieses Mal zu viel ;). Partiell bedeutet teilweise. Der Artikel behauptet nämlich, man solle die entsprechende Codevariante selbst programmieren. Dieser Teil trifft auf mich zu. Jedoch kommt dafür C++ zum Einstz. Dies trifft bei mir nicht zu.

    Techel schrieb:

    *argv zeigt auf den programmpfad, p auf den gefundenen pfadseparator, somit ist p - *argv die distanz der beiden, die anzahl der zeichen bis zum pfadseparator. Damit dieser eingeschlossen wird, noch eins dazuaddiert.

    Danke, das habe ich verstanden. Jedoch ist mir nicht klar, warum die Differenz der Pointer die Anzahl der Zeichen bis zum Pfadseperator angibt. Anscheinend habe ich da noch Defizite im Verständnis der Zeigerarithmetik. Ich subtrahiere ja Adressen von einander. Warum erhalte ich dennnoch das gewünschte Ergebnis?



  • Ah, ich verstehe vermutlich. Diese Art der Rechnung funktioniert nur, weil die Daten hintereinander im Speicher liegt. Ist das richtig?



  • Genau.



  • pointerbub schrieb:

    Das "nicht" ist dieses Mal zu viel ;).

    Deine Postings sind sehr fein zu lesen. Du gibst Dir absolut genug Mühe als Fragesteller.



  • SG1 schrieb:

    Swordfish schrieb:

    Stop using strncpy already!

    Das funktioniert aber nur in C++.

    Eben. Ich bin kein C Programmierer und in C++ brauche ich das normalerweise nicht. Die Probleme von strcpy und strncpy kannte ich aber natürlich schon. Und was ist nun die bevorzugte C Lösung?



  • - strncat mit der Vorbedingung dass ein Leerstring vorliegt (wie im Beispiel oben)
    - sprintf mit precision: sprintf(s,"%.*s",3,"blafasel")

    wobei in beiden Fällen natürlich der Zielspeicher ausreichend groß dimensioniert sein muss, was einem die beiden Varianten NICHT abnehmen.
    Beides geht schon ab C89, garantiert die String-Terminierung und schneidet im Bedarfsfall den Quellstring ab.


Anmelden zum Antworten