Skip to main content
gamedev.net
🔒 Locked

sprintf problems

Started by Kest Aug 15, 2006 at 1:36 AM 77 replies 6.9k views
Original Post
Kest
Kest
Could someone explain why this is happening? char *txt_ptr = some_string_ptr; char text[500]; sprintf( text, "[%02X]", *txt_ptr ); result: text = "[EEFCCB]" (not exactly, but some such thing) some_string_ptr is corrupted, where a character has some feefee type value. But how is it possible to get any more than a two digit hex number when passing a 'char' value? Changing this line fixed the problem: sprintf( text, "[%02X]", static_cast(*txt_ptr) ); Anyone know why it would have been passed as anything else? It's difficult for me to test properly because the situation was only happening because of yet another bug (the invalid string memory) that I fixed. Hopefully the reason is already well known, or I might have to break my code again for educational purposes.
LessBread
LessBread
sprintf( text, "[%02X]", *txt_ptr );

You don't need to dereference the string pointer.

The %X type spec expects an integer, not a string

Try this instead

sprintf( text, "[%s]", txt_ptr );

and if you want a specific character from string, treat the string like an array

sprintf( text, "[%02X]", txt_ptr[0] );
"I thought what I'd do was, I'd pretend I was one of those deaf-mutes." - the Laughing Man
Kest
Kest
Quote:
Original post by LessBread
sprintf( text, "[%02X]", *txt_ptr );

You don't need to dereference the string pointer.

The %X type spec expects an integer, not a string

Try this instead

sprintf( text, "[%s]", txt_ptr );

and if you want a specific character from string, treat the string like an array

sprintf( text, "[%02X]", txt_ptr[0] );


*txt_ptr should be treated exactly the same as txt_ptr[0]. No?
etothex
etothex
I don't know, but just a suggestion, using snprintf in place of sprintf tends to cut down on weird memory bugs like this.

While I'm not sure it would help in your case, it could be that another sprintf elsewhere in your program is screwing up your stack causing strange behavior like this.
LessBread
LessBread
Quote:
Original post by Kest
Quote:
Original post by LessBread
sprintf( text, "[%02X]", *txt_ptr );

You don't need to dereference the string pointer.

The %X type spec expects an integer, not a string

Try this instead

sprintf( text, "[%s]", txt_ptr );

and if you want a specific character from string, treat the string like an array

sprintf( text, "[%02X]", txt_ptr[0] );


*txt_ptr should be treated exactly the same as txt_ptr[0]. No?


Yes, but it wasn't working for him. ???

The array notation is easier to read. [smile]

"I thought what I'd do was, I'd pretend I was one of those deaf-mutes." - the Laughing Man
Ximmer
Ximmer
another thing that could be happening is that a char, being unsigned, if greater than 127 will appear negative to Hexadecimal and thus give you some FFFFFFXX thing. try something like

sprintf(text, "[%02X]", (unsigned char)*txt_ptr);
Kest
Kest
Quote:
Original post by Ximmer
another thing that could be happening is that a char, being unsigned, if greater than 127 will appear negative to Hexadecimal and thus give you some FFFFFFXX thing.

Is that true? This must be it, then. I casted to a byte (static_cast, where byte == unsigned char) to fix it. I just didn't realize the relevance of that when I posted.

I'm a little confused though. I thought bits were bits. How does it matter if I pass it signed or unsigned? I guess 'problem' exists in the way sprintf operates. I would have just assumed hex values of an 8-bit variable would always show up in 8-bit hex representation, double digits.

Oh well, thanks a lot for pointing that out.
Zahlman
Zahlman
If you have static_cast, you have C++. Please welcome yourself to the 21st century.

std::string some_string;std::stringstream ss;ss << '[' << std::hex << some_string[0] << ']';std::string text(ss.str());

Kest
Kest
Quote:
Original post by Zahlman
If you have static_cast, you have C++. Please welcome yourself to the 21st century.

std::string some_string;std::stringstream ss;ss << '[' << std::hex << some_string[0] << ']';std::string text(ss.str());

Is it possible to control the number of digits?

Not to undermine advancement, but isn't this just more complication? My real code was something like
char display_string[8];
sprintf( display_string, "[%02X]", *str_ptr );

How is three declarations more advanced?
ApochPiQ
ApochPiQ
It's largely moot since you should be using stream objects anyways, but this is the source of your signed/unsigned glitch issue.

You can accomplish the same formatting as your sprintf call using fill and width:

std::string some_string;std::stringstream ss;ss << '[';ss.fill('0');ss.width(2);ss << std::hex << some_string[0] << ']';std::string text(ss.str());



This is infinitely better because it is type-safe; that is, there's no chance for you to pass a pointer and have it misread as a number - or, worse, for the code to dereference some random integer thinking it's a pointer to a string.

If you're really hung up on the verbosity, there's always boost::format.
Kest
Kest
That's quite a lot of bloated code to convert a byte into two hex digits. Wouldn't it be easier to just do it manually? I was using sprintf as a lazy way around, but compared to using the string streams, manually converting is the lazy way around.
ApochPiQ
ApochPiQ
No. "Bloated" would be writing some silly manual hack, risking bugs and security exploits, reinforcing horribly bad coding habits, and likely ignoring important internationalization issues, all in the name of duplicating a piece of functionality that already works amazingly well.
Kest
Kest
Yikes! I didn't mean to offend anyone, if that is what has happened. Silly manual hack? Actually, the whole thing is a hack. I'm using hex values to display string character values that my font has no glyphs to display. I needed this because I'm encoding type information, enum values, and other bits of data into texture subset strings for a mesh file format, and I need my mesh editor to display the texture subset names without breaking down. Now that is a hack. I even had to add two (+2) to the values to prevent null characters with my -1 or 0 encoded values. Yeah, hack city.

I'm pretty sure you're confused about what I'm referring to as manual.
( unsigned_value / or % 16 ) + '0' or '7'.
That's all it takes. There are no risky bugs or security exploits to speak of.
Bregma
Bregma
Quote:
Original post by Kest
Could someone explain why this is happening?

char *txt_ptr = some_string_ptr;
char text[500];
sprintf( text, "[%02X]", *txt_ptr );


This is a prime example of one of the reasons why sprintf was superceded by formatting streams in C++. Integral promotion.

The answer you want (as opposed to the right answer: use iostreams) is as follows.
  sprintf(text, "[%02.2X]", txt_ptr[0] & 0xff);

That's going to give you the equivalent of this in C++.
  ostr << '[' << hex <<  setw(2) << setfill('0') << txt_ptr[0] << ']';

It's true the C version saves you 22 keystrokes, but costs hundreds of CPU cycles more (time it!) and gives you wider opportunity to make mistakes like the one you made. Your choice.
Stephen M. Webb
Professional Free Software Developer
Kest
Kest
sprintf wasn't the problem. It was my lack of understanding how it works. Such is true even more so for standard string streams, which I know nothing about.

The keystrokes don't concern me. But adding keystrokes to add more code to add zero more functionality seems like a waste of effort. No offense to anyone, but posting almost any type of algorithm that has been superceded by something else in this particular forum is like walking into a church of a different religion and yelling at everyone that I don't believe in their God. I'm happy with sprintf functionality. That shouldn't be a problem for anyone. That's like preferring to live in a log cabbin or to ride a horse. That's all I need. I have no reason to want to switch to using anything else. If std::string and std::stringstream are better for reasons that are beyound my needs, then good for those that do need it.

I am curious to know one thing, though. Does std::string have any stack based objects? Does it use it's own memory pool? Or does it just do normal allocations?

I appreciate the information very much.
ApochPiQ
ApochPiQ
Quote:
Original post by Kest
Yikes! I didn't mean to offend anyone, if that is what has happened. Silly manual hack? Actually, the whole thing is a hack. I'm using hex values to display string character values that my font has no glyphs to display. I needed this because I'm encoding type information, enum values, and other bits of data into texture subset strings for a mesh file format, and I need my mesh editor to display the texture subset names without breaking down. Now that is a hack. I even had to add two (+2) to the values to prevent null characters with my -1 or 0 encoded values. Yeah, hack city.

I'm pretty sure you're confused about what I'm referring to as manual.
( unsigned_value / or % 16 ) + '0' or '7'.
That's all it takes. There are no risky bugs or security exploits to speak of.



I can't speak for anyone else, but I personally at least am not offended - just trying to save you from yourself [wink]

Off the top of my head, here's the problems in your "manual" version:
  • You have an integer division there. That won't work, at all. It'll round down incorrectly and give bogus results.

  • I have no clue where you came up with '7' as your offset.

  • Even if we fix those problems we get this: (unsignedval % 16) + '0'
    That won't work, because if unsignedval is 10, you get a ':' instead of 'A' or 'a'

  • It makes the very poor assumption that everyone will always use an ASCII-style character table wherein the digits are contiguous. Look up EBCDIC. This assumption is needless, lazy, sloppy, and will lead you to make other similarly poor assumptions in the future if you don't understand why it's so bad. In turn, this perpetuates bad and unportable software.

  • It still doesn't work; feed 122 into it and see what happens. Hint: it doesn't output the correct answer, which is 7A. In fact, even ignoring the above problem of putting out bogus character values, it doesn't work for any number over 15.



... and that doesn't even address the apparent problems in your overall design.
ApochPiQ
ApochPiQ
Quote:
Original post by Kest
sprintf wasn't the problem. It was my lack of understanding how it works. Such is true even more so for standard string streams, which I know nothing about.

The keystrokes don't concern me. But adding keystrokes to add more code to add zero more functionality seems like a waste of effort. No offense to anyone, but posting almost any type of algorithm that has been superceded by something else in this particular forum is like walking into a church of a different religion and yelling at everyone that I don't believe in their God. I'm happy with sprintf functionality. That shouldn't be a problem for anyone. That's like preferring to live in a log cabbin or to ride a horse. That's all I need. I have no reason to want to switch to using anything else. If std::string and std::stringstream are better for reasons that are beyound my needs, then good for those that do need it.

I am curious to know one thing, though. Does std::string have any stack based objects? Does it use it's own memory pool? Or does it just do normal allocations?

I appreciate the information very much.



So educate yourself. Or, if that's not practical or palatable, just copy and paste the perfectly good code that Bregma posted two lines up, and steal it shamelessly without understanding it. It's fine, really - we all do it [wink]

Streams have the benefit that they require less understanding than, say, sprintf. To understand legacy C library functions, you have to know a pretty damned lot about how processors work, memory, pointers, blah, blah, blah. By contrast, to use streams safely, you need to know vanishingly little besides the syntax. Streams are a better abstraction. Programming is all about building on solid and reliable abstractions.


Your analogy is a little off. In reality, you're shunning electric heating because you're content to use a hand grenade to keep yourself warm at night [wink]


By default, std::string does all of its allocations on the freestore, basically the same as you'd get by using new. However, you can change that behavior fairly easily (including using stack-based storage) by providing a custom allocator object.
Bregma
Bregma
Quote:
Original post by Kest
sprintf wasn't the problem. It was my lack of understanding how it works. Such is true even more so for standard string streams, which I know nothing about.

I would disagree: the problem was a lack of knowledge of the subtleties of the C programming language. Subtleties that cause errors the C++ version was designed to prevent. The problem in fact had nothing to do with printf() specifically.
Quote:

That's like preferring to live in a log cabbin or to ride a horse. That's all I need. I have no reason to want to switch to using anything else. If std::string and std::stringstream are better for reasons that are beyound my needs, then good for those that do need it.

I sympathise (hey, I live in a log cabin and can ride a horse). It's just that we suggested you switch for the reason that the C++ version better matched yur needs -- which is to say, helps prevent the sort of error that you encountered which causes a loss of time and enthusiasm for your project. Most of the evangelism you encounter on this (and other) fora is a direct result of hard-earned experience. You don't have to heed it, but from time to time you'll have to admit we told you so.
Quote:

I am curious to know one thing, though. Does std::string have any stack based objects? Does it use it's own memory pool? Or does it just do normal allocations?

std::string by default uses the default allocator std::allocator, which may or may not be a pooled allocator depending on your library's implementation. The standard library also offers a deprecated strstream class that you can use as a formatting stream for C-style char arrays. In the decades I've been developing C++ software, I've never had to use it.
Stephen M. Webb
Professional Free Software Developer
Kest
Kest
Quote:
Original post by ApochPiQ
You have an integer division there. That won't work, at all. It'll round down incorrectly and give bogus results.

That's what makes it work.

Quote:
I have no clue where you came up with '7' as your offset.

10 + '7' is 'A'.

Quote:
It still doesn't work; feed 122 into it and see what happens. Hint: it doesn't output the correct answer, which is 7A. In fact, even ignoring the above problem of putting out bogus character values, it doesn't work for any number over 15.

char[1] = 122 / 16 + ( 122 / 16 < 10 ) ? '0' : '7';
char[2] = 122 % 16 + ( 122 % 16 < 10 ) ? '0' : '7';

Quote:
... and that doesn't even address the apparent problems in your overall design.

What overall design would you be referring to?

Quote:
Or, if that's not practical or palatable, just copy and paste the perfectly good code that Bregma posted two lines up, and steal it shamelessly without understanding it. It's fine, really - we all do it [wink]

That's pretty bad advice. I wouldn't recommend throwing code into places when you have no idea how that code works. And 'we all' doesn't include me. My project is 2.6 megs of source code, composed of 192 source files. That's a lot of characters, and they're all mine. Do you have any idea how lost I would be with this much data if I didn't understand even 10% of it? Granted, I do have to occasionally re-learn my own algorithms to make adjustments because my project has become such a library. But that's pretty effortless compared to trying to manipulate something that I never understood to begin with.

Quote:
To understand legacy C library functions, you have to know a pretty damned lot about how processors work, memory, pointers, blah, blah, blah.

That's extremely exaggerated, and exactly why I posted about elites being far too picky about the tools used to get a simple job done. Your tools have flaws as well, you just don't know it yet. This doesn't bother you, though, because they will likely never effect you in a way that matters to you. Just as sprintf flaws do not effect me in a way that matters to me. But some day, your tools, like mine, will be tossed aside for something else. Then younger people who think like you will be trying to convince other younger people who think like me that std::stringstream is horribly designed, blah, blah, blah. But people like me 'then' are in the exact same boat as people like you 'now'. Get it? I have no good reason to change all of my string operations to use a new system. It would take me weeks, and the benefit, as far as I currently understand it, is nearly zero. Type safety doesn't even come close.

Quote:
Your analogy is a little off. In reality, you're shunning electric heating because you're content to use a hand grenade to keep yourself warm at night [wink]

It's not off, because your electric heater is actually infusing your flesh with tiny amounts of radioactive materials that will eventually cause the extinction of the human species. The difference being that the problems associated with my hand grenade are already well known while you remain completely oblivious. This has nothing to do with my reasoning for sticking to my guns, but apparently it has a lot to do with your reasoning for trying to shame them. Just showing you some light.

Quote:
By default, std::string does all of its allocations on the freestore, basically the same as you'd get by using new. However, you can change that behavior fairly easily (including using stack-based storage) by providing a custom allocator object.

I'll have to look into that. If it's worth the effort to convert, I might switch over some day. It will be horrible, and will take me weeks to convert file formats, change the operation of hundreds of algorithms, and likely hundreds of other miscellaneous changes, but if it's worth it, I'll definitely try.

Quote:
Original post by Bregma
It's just that we suggested you switch for the reason that the C++ version better matched yur needs -- which is to say, helps prevent the sort of error that you encountered which causes a loss of time and enthusiasm for your project.

If I understood that signed char data would result in the signed bit being carried over to the 32nd bit, I would have had no problems. I was already working to convert asci characters into hex values. I'm already on a low enough level to be aware of such things. I was just unfortunate. I've been using the function for five years. One problem in five years is absolutely amazing. Better luck than I've had with plenty of other newer non-string systems.

Quote:
Most of the evangelism you encounter on this (and other) fora is a direct result of hard-earned experience. You don't have to heed it, but from time to time you'll have to admit we told you so.

Hard earned experience with the faults of sprintf? If anyone wants to take the time to give me an example or two about how this function screwed you over, I'll likewise look one up on Google about std::stringstreams. We'll trade back and forth until someone runs out.

Quote:
std::string by default uses the default allocator std::allocator, which may or may not be a pooled allocator depending on your library's implementation. The standard library also offers a deprecated strstream class that you can use as a formatting stream for C-style char arrays. In the decades I've been developing C++ software, I've never had to use it.

You're content with allocating tiny bits of data thousands of times all over your code? If someone invents a super_float that allocates rather than becoming stack based, will you use that in your vertex code? I don't like the idea. I'm not picky about tiny optimizations, but that seems very wasteful.
Zahlman
Zahlman
Quote:
Original post by Bregma
std::string by default uses the default allocator std::allocator, which may or may not be a pooled allocator depending on your library's implementation. The standard library also offers a deprecated strstream class that you can use as a formatting stream for C-style char arrays. In the decades I've been developing C++ software, I've never had to use it.


It also offers a very sexy, non-deprecated stringstream class, in , which does the same thing. It behaves just like a file stream, except it writes to a string in memory.

Voila, polymorphism. No longer any need to specify sprintf or fprintf or plain old printf. Instead, the functionality is implicit according to which object you use.

As for std::string's heap allocations... it does them primarily for the sorts of things that you *should* be doing them for in C code anyway.

Whenever you just blindly write 'char X[MAX_STR_LEN]', you (1) waste most of that memory whenever the string length is much shorter than MAX_STR_LEN (which in many cases will be most of the time), and (2) still crash whenver the string length is that long (remember, null terminator) or longer (which in almost every case is more often than acceptable). Meanwhile, if you don't want to waste time with repeated strlen() calls, you'll eventually find yourself wanting to cache the current string length somewhere... but passing a char* and an int around in a pair, and keeping them in sync manually, gets horribly nasty... so eventually you make a struct to bind them together. Eventually you realize that if you're going to copy this object, the copy ought to have its own copy of the actual string data instead of an alias, so you start using a dynamic allocation anyway, and add another int to track the allocation size (since you might want to preallocate more memory than the current string length, and when the allocation size is exhausted, you need to reallocate)... and guess which wheel you're slowly, badly reinventing?

Topic Locked

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

Sign in to reply to this topic.