C Programming
 
Forums: » Register « |  User CP |  Games |  Calendar |  Members |  FAQs |  Sitemap |  Support | 
User Name:
Password:
Remember me
Go Back   Dev Shed ForumsProgramming LanguagesC Programming

Reply
Add This Thread To:
  Del.icio.us   Digg   Google   Spurl   Blink   Furl   Simpy   Y! MyWeb 
Thread Tools Search this Thread Rate Thread Display Modes
 
Unread Dev Shed Forums Sponsor:
  #1  
Old March 2nd, 2007, 06:54 AM
MassKiller MassKiller is offline
Registered User
Dev Shed Newbie (0 - 499 posts)
 
Join Date: Nov 2005
Posts: 8 MassKiller User rank is Just a Lowly Private (1 - 20 Reputation Level) 
Time spent in forums: 2 h 44 m 28 sec
Reputation Power: 0
String Concatenation With strcpy

Hello, I'm having difficulty's making string concatenation with strcpy or anything that copy's memory from one section to another to work.

I'm attempting to write a performance-based string library for a side-project, and after reviewing my options for string concatenation, strcat/wcscat was not an option.

This is the property of microsoft:
Code:
wchar_t * __cdecl wcscat (
        wchar_t * dst,
        const wchar_t * src
        )
{
        wchar_t * cp = dst;

        while( *cp )
                cp++;                   /* find end of dst */

        while( *cp++ = *src++ ) ;       /* Copy src to end of dst */

        return( dst );                  /* return dst */

}


However, its easy to see why I would want to avoid using strcat/wcscat whenever possible. The amount of branching and looping done is not-linear in terms of assembler code, and there is much overhead when continuously jumping in code.

I've created a C++ class that handles the string, therefore I know at which index of the character array the pointer points to, that I want to insert data. Therefore I've used strcpy and or memcpy to achieve this task.

However there is a slight problem.

Code:
FPtrString& operator+=(const FTCHAR* sconcat)
	{
		FSIZE_T	InsertCount = XEngStrlen(sconcat);
		if(Num)
		{
			FUINT	InsertIndex = Num - 1;
			Add(InsertCount);
			XEngStrcpy_s((FTCHAR*)&(*this)(InsertIndex),Max-InsertIndex,sconcat);
			//XEngMemcpy(&((*this)(InsertIndex)),(void*)sconcat,InsertCount*sizeof(FTCHAR));
		}
		else if(*sconcat)
		{
			Add(InsertCount);
			//XEngStrcpy_s((FTCHAR*)Data,Max,sconcat);
			XEngMemcpy(Data,(void*)sconcat,InsertCount*sizeof(FTCHAR));
		}
		printf("\n");

		return *this;
	}


I've created a test:

Code:
	FPtrString	sTestConcatFull		= FPtrString(_STR("Test the concatination of a string."));
	sTestConcatFull += _STR("Test the concatination 1. ");
	sTestConcatFull += _STR("ReTest the concatination 2. ");
	printf("%ls\n",_OUT_STR(sTestConcatZero));


which results in the following output:

Code:
Test the concatination of a string.Test the concatination 1.


As you see the first string is concatenated perfectly, but the second one appears as it doesn't happen at all. The wcscat/strcat function work without fail, all the time. However something is happening that I just havent quite yet spotted.

The Add() function simply allocates the space needed with realloc(), the (*this)(Index) is an operator operator(int) which returns the value ((FTCHAR*)Data)[Index]

Reply With Quote
  #2  
Old March 2nd, 2007, 07:49 AM
salem's Avatar
salem salem is offline
Contributing User
Dev Shed Intermediate (1500 - 1999 posts)
 
Join Date: Jun 2005
Posts: 1,760 salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)  Folding Points: 153 Folding Title: Novice Folder
Time spent in forums: 1 Month 6 Days 22 h 26 m 11 sec
Reputation Power: 319
My guess is you forgot to copy the \0 if you're basing the amount to copy on the number of characters.

Or you overcounted it, and ended up with a string in memory which looks like
"hello\0world\0"
That would have all the appearance of "world" being completely missing from the output.

Also,
strcat( dest, src );

Can be replaced with
size_t len = strlen(dest);
strcpy( &dest[len], src );


Which, if you keep len up to date as a class member variable should be pretty good.
__________________
If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.

Reply With Quote
  #3  
Old March 2nd, 2007, 08:54 AM
Lux Perpetua Lux Perpetua is offline
Contributing User
Dev Shed Beginner (1000 - 1499 posts)
 
Join Date: Feb 2004
Location: San Francisco Bay
Posts: 1,474 Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level)Lux Perpetua User rank is Major (30000 - 40000 Reputation Level) 
Time spent in forums: 3 Weeks 5 Days 23 h 29 m 36 sec
Reputation Power: 377
Quote:
However, its easy to see why I would want to avoid using strcat/wcscat whenever possible. The amount of branching and looping done is not-linear in terms of assembler code, and there is much overhead when continuously jumping in code.
Not linear? How do you figure?

Reply With Quote
  #4  
Old March 2nd, 2007, 02:40 PM
Purple Avenger Purple Avenger is offline
Contributing User
Dev Shed Newbie (0 - 499 posts)
 
Join Date: Dec 2006
Posts: 271 Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level)Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level)Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level)Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level)Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level) 
Time spent in forums: 2 Days 1 h 51 m 31 sec
Reputation Power: 9
If performance is paramount, lose the C style strings and adopt Pascal style.

Reply With Quote
  #5  
Old March 2nd, 2007, 03:12 PM
clifford's Avatar
clifford clifford is offline
Contributing User
Dev Shed Frequenter (2500 - 2999 posts)
 
Join Date: Aug 2003
Location: UK
Posts: 2,797 clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level) 
Time spent in forums: 2 Weeks 2 Days 11 h 49 m 54 sec
Reputation Power: 448
Quote:
Originally Posted by Purple Avenger
If performance is paramount, lose the C style strings and adopt Pascal style.

Agreed.

If you creating your own string class you don't have to stick with the C string convention, an unterminated array and a count member would be more efficient. You should probably not use the string library as the basis for anything - you have already found strcat() to be wanting, and it is, but not only in the way you think - the MS implementation you posted is sub-optimal, presumably relying on compiler optimisation to 'fix' it. Do the copy using an unrolled loop and Duff's Device(with the Stroustrup modification) - it aint't pretty but it is efficient - not for 'every day' code I suggest!

Clifford

Reply With Quote
  #6  
Old March 2nd, 2007, 03:41 PM
MassKiller MassKiller is offline
Registered User
Dev Shed Newbie (0 - 499 posts)
 
Join Date: Nov 2005
Posts: 8 MassKiller User rank is Just a Lowly Private (1 - 20 Reputation Level) 
Time spent in forums: 2 h 44 m 28 sec
Reputation Power: 0
Quote:
Originally Posted by clifford
Agreed.

If you creating your own string class you don't have to stick with the C string convention, an unterminated array and a count member would be more efficient. You should probably not use the string library as the basis for anything - you have already found strcat() to be wanting, and it is, but not only in the way you think - the MS implementation you posted is sub-optimal, presumably relying on compiler optimisation to 'fix' it. Do the copy using an unrolled loop and Duff's Device(with the Stroustrup modification) - it aint't pretty but it is efficient - not for 'every day' code I suggest!

Clifford


The class implementation is an array itself. It extends an array class, more specifically a template array class. The first data-member is a pointer a void pointer in the array class, thus I can simply use the object itself as a string.

Its a dynamically adjusting array, if you mean unterminated in that sense. At some point since the string is used for all sorts of purposes include File I/O, or other contents outputted, it has to have a null-terminating character at some point. Otherwise some very nasty errors arise from accessing unallocated memory.

Its not very original but the Memcpy function is an assembler optimized function:

Code:
	__asm
	{
		mov		ecx, [nSize]
		mov		esi, [pSource]
		mov		edi, [pDestination]
		mov     ebx, ecx
		shr     ecx, 2							// Divide the count by 4
		and     ebx, 3							// Take only the right-most two bits (2^1, 2^0) (A.K.A remainder)
		rep     movsd							// Move memory in 32-bit blocks by the destination divided by 4
		mov     ecx, ebx
		rep     movsb							// Move any remaining 8-bit blocks (maximum 3)
	}

You can appreciate the fact that it copy's in sets of 4, and copy's the trailing bytes single by single to a maximum of 3 at any given point of time.

This assembly assumes NO overlapping buffers.

I hope to eliminate the string library eventually, however branching is something I wouldn't want to touch ever using inline assembly.
Quote:
Originally Posted by Lux Perpetua
Not linear? How do you figure?


Non linear code in assembler, results from the If, While, For, and possibly other pieces of high-level language programming. When it comes to assembler, you want to eliminate branching, eliminate ifs.

Using ifs, whiles, and for's usually result in assembler looking like:

test <register>,<register>

or some other comparison operator of an instruction set, the following instruction being

jmp <addr>

to execute a condition statement.

A loop simply jumps back to the initial portion of the loop

Addr:
<do>
jmp <Addr>

with the condition evaluation being sometime before the jump or inside the area jumping outside of the loop.

Branching is considered poor in terms of performance optimization, see the memcpy above. Although it is repetitive no overhead is spent on a jump instruction.

You may want to read: Branch Elimination Guide

For further information about non-linear assembler code.


Quote:
Originally Posted by salem
My guess is you forgot to copy the \0 if you're basing the amount to copy on the number of characters.

Or you overcounted it, and ended up with a string in memory which looks like
"hello\0world\0"
That would have all the appearance of "world" being completely missing from the output.

Also,
strcat( dest, src );

Can be replaced with
size_t len = strlen(dest);
strcpy( &dest[len], src );


Which, if you keep len up to date as a class member variable should be pretty good.


Thanks, I used your advice and found that there was a null-terminator somewhere inbetween. Go figure Thank you for your assistance.

Reply With Quote
  #7  
Old March 2nd, 2007, 05:22 PM
salem's Avatar
salem salem is offline
Contributing User
Dev Shed Intermediate (1500 - 1999 posts)
 
Join Date: Jun 2005
Posts: 1,760 salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)salem User rank is Major (30000 - 40000 Reputation Level)  Folding Points: 153 Folding Title: Novice Folder
Time spent in forums: 1 Month 6 Days 22 h 26 m 11 sec
Reputation Power: 319
Thus proving once again that you should be looking to make it right before trying to make it fast. How much time have you spent chasing this bug you added seeking a few extra clock ticks?

I mean, if there is any file access going on in your program then messing around with the nano-seconds of processor instruction timing isn't going to make a bean of difference to the milliseconds (1E6 times slower) that disk seek times are measured in.

To be honest, this reeks of premature optimisation.

a) is your program finished and fully debugged?

b) do you have real world data to test with (not test samples)?

c) have you run your finished program with real data under a profiling tool - if not, you're just guessing.

Why should you wait until the program is finished?
1. The program may be quick enough for its purpose anyway.
2. The profiler will tell you where the real problems are, not just where your subjective guesses think they might be.
3. Since the program already works, you have a baseline to test any "optimisations" to see whether they are in fact an improvement and also bug free in themselves.

http://www.prism.uvsq.fr/~cedb/local_copies/lee.html
Mostly, I just stick to the first couple of points and let today's optimising compilers figure out all the little tricks which can improve the code.

Reply With Quote
  #8  
Old March 2nd, 2007, 06:35 PM
Purple Avenger Purple Avenger is offline
Contributing User
Dev Shed Newbie (0 - 499 posts)
 
Join Date: Dec 2006
Posts: 271 Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level)Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level)Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level)Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level)Purple Avenger User rank is Sergeant (500 - 2000 Reputation Level) 
Time spent in forums: 2 Days 1 h 51 m 31 sec
Reputation Power: 9
Quote:
Originally Posted by MassKiller
At some point since the string is used for all sorts of purposes include File I/O, or other contents outputted, it has to have a null-terminating character at some point.


Just ensure it has one not considered as part of the length.

Reply With Quote
  #9  
Old March 2nd, 2007, 06:46 PM
sizablegrin's Avatar
sizablegrin sizablegrin is online now
Stubborn ol' L'User
Click here for more information.
 
Join Date: Jun 2005
Posts: 3,681 sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level)sizablegrin User rank is General 14th Grade (Above 100000 Reputation Level) 
Time spent in forums: 1 Month 2 Weeks 5 Days 23 h 4 m 32 sec
Reputation Power: 1919
The suggestion to use strlen, rather than running out to the nul from the beginning, is inane, as that's precisely what strlen does. The best advice given in this thread is by Salem, regarding optimization. Additional material on that may be found in the Commonly Asked Questions thread, by a fellow named Mitakeet.

Reply With Quote
  #10  
Old March 3rd, 2007, 04:22 AM
clifford's Avatar
clifford clifford is offline
Contributing User
Dev Shed Frequenter (2500 - 2999 posts)
 
Join Date: Aug 2003
Location: UK
Posts: 2,797 clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level)clifford User rank is Lieutenant Colonel (40000 - 50000 Reputation Level) 
Time spent in forums: 2 Weeks 2 Days 11 h 49 m 54 sec
Reputation Power: 448
Quote:
Originally Posted by MassKiller
At some point since the string is used for all sorts of purposes include File I/O, or other contents outputted, it has to have a null-terminating character at some point.


Indeed at some point, but that can be added only when necessary, after the pattern of the standard string::c_str() function for example. For output, if you overload ostream::operator<<, you don't need a special terminator as your overloaded function would be aware of the internal representation (via a friend relationship).

Clifford

Last edited by clifford : March 3rd, 2007 at 04:18 PM.

Reply With Quote
  #11  
Old March 3rd, 2007, 11:26 AM
weazzle weazzle is offline
Contributing User
Dev Shed Newbie (0 - 499 posts)
 
Join Date: Dec 2006
Location: Albuquerque, NM
Posts: 115 weazzle User rank is Sergeant (500 - 2000 Reputation Level)weazzle User rank is Sergeant (500 - 2000 Reputation Level)weazzle User rank is Sergeant (500 - 2000 Reputation Level)weazzle User rank is Sergeant (500 - 2000 Reputation Level)weazzle User rank is Sergeant (500 - 2000 Reputation Level) 
Time spent in forums: 2 Days 4 h 15 m 32 sec
Reputation Power: 21
Send a message via AIM to weazzle
While the memcpy function is optimized, you have to assume that you already know the length of the string you are appending. If you do not, you will have to use strlen. In which case you should just use strcat. If you know the length of your strings without having to use strlen, then memcpy is certainly the faster option.

Reply With Quote
Reply

Viewing: Dev Shed ForumsProgramming LanguagesC Programming > String Concatenation With strcpy


Thread Tools  Search this Thread 
Search this Thread:

Advanced Search
Display Modes  Rate This Thread 
Rate This Thread:


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
View Your Warnings | New Posts | Latest News | Latest Threads | Shoutbox
Forum Jump