strcpy_s bringt mich zur Ver­zweif­lung



  • Hallo zusammen,

    nachdem ich es mit Eurer Hilfe geschafft habe, den Oracle Precompiler in mein Visual Studio 2015 Projekt einzubinden, scheitere ich gleich beim nächsten Schritt.

    Ich möchte den Befehl strcpy_s() einsetzen (weil der alte Befehl strcpy unsicher ist).

    Der Link zu Dokumentation:
    https://msdn.microsoft.com/en-us/library/td1esda9.aspx

    errno_t strcpy_s(  
       char	 	 	 *strDestination,  
       size_t	 	 numberOfElements,  
       const char	 *strSource   
    );
    

    Egal, was ich in den drei Parametern übergebe, es wird nur ein Zeichen nach strDestination (strDestination = vcBenutzer.arr = ucZiel) kopiert. Warum?

    Hier mal mein Code-Beispiel

    BOOL CHallo::connectDB()
    {
    	CString	csBenutzer = _T("SCOTT");
    	CString	csPasswort = _T("TIGER");
    	CString	csDatenbank = _T("Datenbank");
    
    	//Oracle Variable.
    	EXEC SQL BEGIN DECLARE SECTION;
    		VARCHAR vcBenutzer[32];	 	 //struct
    		VARCHAR vcPasswort[32];	 	 //struct
    		VARCHAR vcDatenbank[32]; 	 //struct
    	EXEC SQL END DECLARE SECTION;
    
    	...
    
    	//#############################################################
    	//username.len = strlen(strcpy((char *)username.arr, "SCOTT"));   //Oracle Database Release 18 Programmer's Guide
    	//password.len = strlen(strcpy((char *)password.arr, "TIGER"));   //Oracle Database Release 18 Programmer's Guide
    	//#############################################################
    	vcBenutzer.len	= CString2Varchar(vcBenutzer.arr, csBenutzer);
    	vcPasswort.len	= CString2Varchar(vcPasswort.arr, csPasswort);
    	vcDatenbank.len	= CString2Varchar(vcDatenbank.arr, csDatenbank);
    
    	...
    }
    
    int CHallo::CString2Varchar(unsigned char ucZiel[], CString csQuelle)
    {
    	errno_t	iErrNo	= -1;
    	size_t	iGetLen	= csQuelle.GetLength() + 1;	//Debug
    	size_t	iSizeOf	= sizeof(ucZiel);			//Debug
    	int		iLaenge	= -1;
    
    	...
    
    	//	Veraltet! strcpy()
    	//#############################################################
    	//username.len = strlen(strcpy((char *)username.arr, "SCOTT"));	//Oracle Database Release 18 Programmer's Guide
    	//password.len = strlen(strcpy((char *)password.arr, "TIGER"));	//Oracle Database Release 18 Programmer's Guide
    	//#############################################################
    
    //	if (0 == (iErrNo = strcpy_s((char *)ucZiel, (csQuelle.GetLength() + 1), (char *)csQuelle.GetBuffer(csQuelle.GetLength() + 1))))
    //	if (0 == (iErrNo = strcpy_s((char *)ucZiel, sizeof(ucZiel), (char *)csQuelle.GetBuffer(csQuelle.GetLength()))))
    //	if (0 == (iErrNo = strcpy_s((char *)ucZiel, sizeof(ucZiel), (char *)csQuelle.GetBuffer(0))))
    //	if (0 == (iErrNo = strcpy_s((char *)ucZiel, 3, (char *)(LPCTSTR)csQuelle)))
    	if (0 == (iErrNo = strcpy_s((char *)ucZiel, iSizeOf, (char *)(LPCTSTR)csQuelle)))
    	{
    		csQuelle.ReleaseBuffer();
    		iLaenge = csQuelle.GetLength();
    	}
    
    	return iLaenge;
    }
    

    Oracle Database Release 18 Programmer's Guide
    https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpcc/datatypes-and-host-variables.html#GUID-2CFEFD3E-61E4-4B86-92E7-35DCFAFFFA9D

    4.4.1 VARCHAR Variable Declaration
    Think of a VARCHAR as an extended C type or pre - declared struct.
    For example, the precompiler expands the VARCHAR declaration

    VARCHAR username[20];

    into the following struct with array and length members :

    struct
    {
    unsigned short len;
    unsigned char arr[20];
    } username;


  • Mod

    Das liegt nicht an strcpy_s sondern an Dir!

    Ursache: Weil Du ein Unicode Programm hast!

    Das ist Blödsinn:

    (char *)(LPCTSTR)csQuelle)))
    

    Warum verwendest Du überhaupt einen cast?
    Sowas überkleistert gleich solche Fehler.
    csQuelle.GetString() ist auch besser als der cast!

    CString ist in einem Unicode Programm eben Unicode, wenn Die char* haben willst dann entweder konvertieren oder eben gleich MBSC verwenden.
    Wenn Du Unicode und MBSC gleichzeitig verwenden willst verwende entsprechend CStringA oder CStringW

    Du hast außerdem einen Debugger und kannst Dir das ansehen und Du wirst dann feststellen, dass CString intern einen wchar_t* hat...

    casts nur nehmen wenn nötig und nicht um eine Warnung des Compilers zu überkleistern!



  • Hallo Martin,

    ich bin langjähriger Benutzer von VC++6.0 und versuche, mich in Visual Studio 2015 einzuarbeiten. Hier in/mit Visual Studio 2015 ist alles so anders und ungewohnt.

    CStringA und CStringW kenne ich (noch) nicht, CString.GetString() ebensowenig. Diese Dinge gibt es auch in VC++6.0 nicht (habe gerade nochmal die Hilfe bemüht).

    Ich habe zuerst die Google-Suchmaschine bemüht und habe mir Beispiele und Beiträge zu strcpy_s (gibt es ebenfalls nicht in VC++6.0) gesucht, gelesen, ausprobiert (und dann verzweifelend rumprobiert), bevor ich hier um Hilfe nachfragte.

    Ach ja, einen Debugger kann ich bedienen. Darum schrieb ich ja, dass nur ein Zeichen (das erste Zeichen) nach ucZiel übergeben wird. 😋


  • Mod

    Das wichtigste, das du dir erst einmal merken solltest, ist, dass Casts ganz, ganz vorsichtig zu setzen sind! Casts sind nicht dazu da, unpassende Daten passend zu machen. Sie sind dazu da, damit zueinander passende Daten, die fälschlich(!) als unpassend erkannt wurden, zueinander gebracht werden können.

    Wenn der Compiler dir sagt, dass Datentypen nicht zueinander passen, dann hat er damit auch Recht. Wenn du irgendeine Zusatzinformation hast, die der Compiler nicht wissen kann, und dir dadurch absolut 100% sicher bist, dass die Daten hinter den inkompatibel aussehenden Typen doch zueinander passen, dann kannst du einen Cast setzen.

    Wenn du hingegen einfach einen Cast setzt, damit der Compiler die Schnauze hält, anstatt dich auf unpassende Typen hinzuweisen, dann ist das Ergebnis natürlich Murks. Was sonst?



  • Volle Zustimmung.

    Reden wir über "username.len = strlen(strcpy((char *)username.arr, "SCOTT"));" oder meine mißglücken Versuche bei der Anwendung von strcpy_s?

    Im Beispiel wird ja auch gecastet (unsignde char* nach char*) und das Beispiel steht so in der offiziellen Dokumentation zum Oracle-Precompiler Pro*C/C++.

    In meinen Versuchen mit strcpy_s habe ich die Dokumentation von "strcpy_s, wcscpy_s, _mbscpy_s" https://msdn.microsoft.com/en-us/library/td1esda9.aspx gelesen und ich versuche, das sauber umzusetzen. Das ist mir bis jetzt nicht gelungen.

    Als Ausgangsmaterial habe ich nun mal einen CString und um mit der Oracle-Datenbank zu sprechen brauche ich einen Varchar (bzw. einen unsigned char*). Wie mache ich es richtig? Der Vorschlag mit csQuelle.GetString() war jedenfalls nicht zielführend.


  • Mod

    Ich bezog mich vor allem auf das, was Martin Richter ansprach:

    (char *)(LPCTSTR)csQuelle)))
    

    Der Horror.

    Hingegen ist ein Cast von unsigned char* nach char* ein gutes Beispiel für einen Cast, der in Ordnung gehen kann. Auf dem Papier sind die Typen inkompatibel, aber wenn du genau weißt, dass du nur den kompatiblen Teil des Wertebereichs benutzen wirst (was der Compiler nicht wissen kann), dann kann man hier erzwingen, dass dies doch zugelassen wird.


  • Mod

    Es ist mir egal von wo Du umsteigst... 😉
    Selbst in VC 6.0 wäre Dein Code schiefgegangen.
    In VC 6 war es sicherer den .operator LPCTSTR aufzurufen als einen cast zu verwenden. Außer Du benutzt static_cast!
    Oder Du verwendest static_cast! Ja den gab es auch schon unter VC6.

    Ein gutes Beispiel bei einem mismatch von unsigned char* und char *

    Der folgende Code wäre für mich ein no go und ein Programmierer in meinem Team riskiert dann einen Finger zu verlieren, wenn er mir so was unterjubelt.

    username.len = strlen(strcpy((char *)username.arr, "SCOTT"));
    

    Warum? Man sieht nicht was man macht obwohl das Problem klar ist.

    Wie wird das ganze besser? Wenn ich regelmässig solche "converts" habe, dann schreibe ich mir eine einfache Inline Funktion. Die ist Typsicher! Ein cast ist es nie!

    inline const char* CharSignedToUnsigned(const unsigned char* in)
    {
        return reinterpret_cast<const unsigned char*>(in);
    }
    

    Daraus folgt

    username.len = strlen(strcpy(CharSignedToUnsigned(username.arr), "SCOTT"));
    

    Denn! Das würde mein Code merken:

    username.len = strlen(strcpy(CharSignedToUnsigned(username), "SCOTT"));
    

    Deiner nicht!

    username.len = strlen(strcpy((char *)username, "SCOTT"));
    

    Holzhammer und BUMM! Wenn username eine Struktur ist und arr eben nicht der erste Member!

    Merke: Casts sind böse und das waren sie auch schon zu Zeiten von VC6!
    Und wenn casts dann bitte nur die entsprechenden static_cast, const_cast und wenn es nicht anders geht reinterpret_cast



  • Erstmal herzlichen Dank für die harte und konstruktive Kritik. Das war wohl nötig.

    username.len = strlen(strcpy((char *)username.arr, "SCOTT"));
    

    Das hier habe nicht ich verbrochen, sondern ein Programmierer bei Oracle. Ich habe dieses Beispiel aus der aktuellen Oracle-Dokumentation vom Februar 2018! Allerdings steht das Beispiel schon seit vielen Jahren so in den verschiedenen Oracledokumentationen und findet sich unter dem Kapitel "12.3.1 cppdemo1.pc".
    https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpcc/C-Plus-Plus-Applications.html#GUID-F2D26A5A-6AF0-4B49-B250-212A0419D0D4

    Das, was ich an Code-Beispielen sonst noch gezeigt habe "if (0 == (iErrNo = strcpy_s(...)" geht auf mich und ist einer letztendlich sinnlosen Herumproberei geschuldet. Das verspreche ich Euch, besseren Code abzuliefern.

    Ich gehe jetzt mal in mich, überdenke Eure Ratschläge und verbessere meine Kenntnisse.


Log in to reply