Skip to main content
GameDev.net gamedev.net
🔒 Locked

String handling in C

Started by dave j Mar 10, 2013 at 10:53 PM 31 replies 9.3k views
Original Post
dave j
dave j
In a former colleague's code about 14 years ago:

sprintf(str, "%s %s %s", a, b);

The value of str was displayed on screen after this. This had been live at a bank for a couple of years before it was discovered. The reason it took so long to notice is that the value on the stack that was used for the third string's address happened to point to a byte containing zero.
Alpha_ProgDes
Alpha_ProgDes

Does any C or even C++ compiler catch a mismatch like that? That's a terrible bug to have. Code reviews FTW.

Beginner in Game Development?  Read here. And read here.  
Paradigm Shifter
Paradigm Shifter

gcc looks at the format string for printf & co and gives a warning I think. It has to be built in to the compiler (or via metadata related to a function declaration) since using variable length argument lists removes all checking to do with type and number of arguments...

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley
21st Century Moose
21st Century Moose

"String handling in C" is a coding horror all on it's own - no further comment is necessary.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls. 
TheChubu
TheChubu

Care to elaborate? :D

I purposely stayed away from C's formatted output for the brief time I was learning C++.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"   My journals: dustArtemis ECS framework and 
Alternate-E
Alternate-E

Could be worse, could be a web of pointers so convoluted that they point to nothing while trying to point to some embedded function, with an over-called string in it that still works for some reason. *shudders*

Editor // Joy-Toilet.com Anything But Shitty Entertainment!
patrrr
patrrr

gcc looks at the format string for printf & co and gives a warning I think. It has to be built in to the compiler (or via metadata related to a function declaration) since using variable length argument lists removes all checking to do with type and number of arguments...

Clang also does this, plus, it also checks that the format string is correct with respect to argument types. Really handy!

21st Century Moose
21st Century Moose

Care to elaborate? biggrin.png

I purposely stayed away from C's formatted output for the brief time I was learning C++.

You can overflow the buffer at any time, you don't know how long it is, you need to walk over the entire string in order to do any operation (which can easily lead to O(n2) algorithms) - strings in C basically contain everything that one should not do if one was going to design a string library. See http://en.wikipedia.org/wiki/C_string_handling#Criticism and http://www.joelonsoftware.com/articles/fog0000000319.html for more.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls. 
dave j
dave j

Does any C or even C++ compiler catch a mismatch like that? That's a terrible bug to have. Code reviews FTW.


This was an IBM C compiler which didn't perform any such checks. I don't think any did at the time.

The team were supposed to do code reviews and should have picked this up then. My job was developer support which included solving "our code's crashing and we don't know why" type problems. In this case I was given a memory dump and asked to figure out what was going wrong.
dave j
dave j

Care to elaborate? :D

I purposely stayed away from C's formatted output for the brief time I was learning C++.

Each %s in the string means there should be another parameter that is a pointer to a string. The line should look like:

sprintf(str, "%s %s %s", a, b, c);
Because the function is expecting another parameter on the stack to go with the third %s, it will use whatever is in the next memory location after the b. This could be anything!
Sik_the_hedgehog
Sik_the_hedgehog


Care to elaborate? biggrin.png

I purposely stayed away from C's formatted output for the brief time I was learning C++.

You can overflow the buffer at any time, you don't know how long it is, you need to walk over the entire string in order to do any operation (which can easily lead to O(n2) algorithms) - strings in C basically contain everything that one should not do if one was going to design a string library. See http://en.wikipedia.org/wiki/C_string_handling#Criticism and http://www.joelonsoftware.com/articles/fog0000000319.html for more.

Actually it isn't just strings, it's arrays in general that suffer from that (actually with generic arrays it's even worse - with strings at least you can expect it to stop when there's a zero, with an array the only way to be 100% sure of the length is to pass it separately). Strings just happen to be one specific application of an array (to the point all array operations work on them).

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.
wintertime
wintertime

I always wonder when people just use printf-like functions with %s or even without a single %. Dont they know there are things like fputs, strcpy, strcat which dont need to parse possibly wrong format strings?

Sik_the_hedgehog
Sik_the_hedgehog

Then the code above would have been equivalent to this (bug included):


strcpy(str, a);
strcat(str, " ");
strcat(str, b);
strcat(str, " ");

I know that isn't optimal (it'll read all of the string thrice) and you can make it faster, but then the code becomes less clear and can be much harder to read. Not like this code is not error prone anyway - I wonder how many programmers end up reading the strcpy as strcat. So in that sense sprintf looks like a good thing because it makes the code more concise without giving up much on readability (if we're talking about just a single string then it's overkill though).

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.
Kylotan
Kylotan

Care to elaborate? biggrin.png

I purposely stayed away from C's formatted output for the brief time I was learning C++.

C doesn't have strings. It has arrays of characters, and some fancy goggles for the programmer which make those arrays look and act a bit like strings if you're very careful.

Khatharr
Khatharr

Then the code above would have been equivalent to this (bug included):

strcpy(str, a);
strcat(str, " ");
strcat(str, b);
strcat(str, " ");
I know that isn't optimal (it'll read all of the string thrice) and you can make it faster, but then the code becomes less clear and can be much harder to read. Not like this code is not error prone anyway - I wonder how many programmers end up reading the strcpy as strcat. So in that sense sprintf looks like a good thing because it makes the code more concise without giving up much on readability (if we're talking about just a single string then it's overkill though).


char* unknown;
strcpy(str, a);
strcat(str, " ");
strcat(str, b);
strcat(str, " ");
srtcat(str, unknown);
I fixed your bug for you, sir.
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
Hodgman
Hodgman

"String handling in C" is a coding horror all on it's own - no further comment is necessary.

I'd say "string manipulation in C" is a coding horror, but consuming read-only strings in C is refreshingly lacking in unnecessary abstraction.

In my C++ engine, I don't use any string classes. Instead I choose to use const char* for any strings, simply because I don't do any string manipulation at all, so the simplest solution works fine wink.png

[edit] to clarify, this also means not using any of the C standard library functions that work on strings [/edit]

TheChubu
TheChubu


Care to elaborate? biggrin.png

I purposely stayed away from C's formatted output for the brief time I was learning C++.

You can overflow the buffer at any time, you don't know how long it is, you need to walk over the entire string in order to do any operation (which can easily lead to O(n2) algorithms) - strings in C basically contain everything that one should not do if one was going to design a string library. See http://en.wikipedia.org/wiki/C_string_handling#Criticism and http://www.joelonsoftware.com/articles/fog0000000319.html for more.


Care to elaborate? biggrin.png

I purposely stayed away from C's formatted output for the brief time I was learning C++.

Each %s in the string means there should be another parameter that is a pointer to a string. The line should look like:


sprintf(str, "%s %s %s", a, b, c);
Because the function is expecting another parameter on the stack to go with the third %s, it will use whatever is in the next memory location after the b. This could be anything!


Care to elaborate? biggrin.png

I purposely stayed away from C's formatted output for the brief time I was learning C++.

C doesn't have strings. It has arrays of characters, and some fancy goggles for the programmer which make those arrays look and act a bit like strings if you're very careful.

Oh I see then it might trash the memory, thanks!

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"   My journals: dustArtemis ECS framework and 
Sik_the_hedgehog
Sik_the_hedgehog


"String handling in C" is a coding horror all on it's own - no further comment is necessary.

I'd say "string manipulation in C" is a coding horror, but consuming read-only strings in C is refreshingly lacking in unnecessary abstraction.

Only as long as you're reading it sequentially. If you ever need to know the length, you'll need to use strlen which traverses the entire string (and thereby is a performance penalty), and if you're using a variable-length encoding such as UTF-8, consider yourself screwed as all the functions work on chars rather than the proper characters (e.g. in that case strlen would return the number of bytes, rather than the number of characters).

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.
dave j
dave j

Oh I see then it might trash the memory, thanks!


It's not just that. If the value that happens to be on the stack is invalid if used as an address, it would crash the program.
21st Century Moose
21st Century Moose

If you're lucky it trashes the memory and gives you a nice clean crash at the point where things started going wrong.

More normally, it seems to work OK but at some arbitrary point later and in a completely different part of your code you start getting weird things happen.

Yeah, strings are just arrays, but I think it's worth singling out strings here because if you're using an array you've normally got an extra level of awareness of what you're doing, whereas the CRT tries to look like it's pretending that strings are some kind of special case or something different, which may lead the unwary into thinking that they're OK.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls. 

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.