views:

237

answers:

4

I was just spending my whole day debugging a random bug when i finally realized the Problem was sscanf being called from multiple threads.

I confirmed by running the following code which works as expected on Snow Leopard but produces very strange results on my iphone with os 3.1.2. It also works fine in the Simulator.

On the iPhone the parsed number will be a somewhat random combination of the digits used in the strings.

It would be very helpfull if anyone could check if this is a general Problem or if it's a mistake on my side.

- (void)testIt
 {
    [NSThread detachNewThreadSelector:@selector(scanfTest) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(scanfTest) toTarget:self withObject:nil];
}

- (void)scanfTest
{
    while (true)
    {
      float value = 0.0f;
      sscanf("456", "%f", &value);
      sscanf( "1.63", "%f", &value);
      if (value != 1.63f)
        NSLog(@"strange value is %f", value);
    }
}

I did some further checking and it appears only floating point numbers are an issue.
This code works

- (void)scanfTest4
{
    while (true)
    {
     int year = 0;
     int month = 0;
     int day = 0;
     sscanf("20090131", "%4d%2d%2d", &year, &month, &day);
     sscanf("19840715", "%4d%2d%2d", &year, &month, &day);
     if (year != 1984 || month != 7 || day != 15)
      NSLog(@"bla");
    }
}

And this code fails with the same random digit issues

- (void)scanfTest4
{
    while (true)
    {
     int year = 0;
     int month = 0;
     float day = 0.0f;
     sscanf("20090131", "%4d%2d%2f", &year, &month, &day);
     sscanf("19840715", "%4d%2d%2f", &year, &month, &day);
     if (year != 1984 || month != 7 || day != 15.0f)
      NSLog(@"bla");
    }
}
A: 

sscanf is not thread safe PERIOD. it was written before threads existed its design requires it to use internal static temp variables. the same is true for sprintf.

I your case, sscanf is overkill anyway. just use atof instead.

John Knoeller
Don't use atof. Use the standard C `strtof( )` function instead, which has much simpler error handling and is generally more usable.
Stephen Canon
Could you explain what in the definition of sscanf requires it to use static variables?
Steve Jessop
@Steve Jessop, it doesn't, but it needs to keep track of how far in the format string it is and so on. It is easy, I guess, to just use a static. But I am kind of stumped myself at the result to questioner gets.
Amigable Clark Kant
@Steve Jessop. so perhaps _requires_ is too strong. It's certainly possible to imagine an implemention that doesn't use statics. But it does need a fair bit of temp storage and it calls a lot of the other crt code as helpers. the typical implementation turns your input string into a 'pretend' file and then passes it on to fscanf (or rather, it and fscanf share a common worker). Tearing out all of the statics in the original crt is quite a job, and you would STILL not have protected yourself from buffer overflows.
John Knoeller
It can't be too hard to do without statics of you already did it for OSX and the Simulator I guess.
zupamario
Don't use `atof`, it's deprecated. Use `strtod()` instead.
Adam Rosenfield
+2  A: 

SUSv2 says (Threads):

All interfaces defined by this specification will be thread-safe, except that the following interfaces need not be thread-safe

sscanf() is not on the list of interfaces which need not be thread-safe.

This is not to say that the iPhone is SUSv2-compliant, but I think at least it explains why your code should be expected to work on Snow Leopard. Also I don't have a more recent POSIX spec to hand, so I'm taking a bit of a risk in assuming it hasn't changed since 1997.

Steve Jessop
I checked SUSv2 too and was therfore a little confused by John's "sscanf is not thread safe PERIOD."
zupamario
sscanf is not one of the POSIX functions I've ever had the lid off. I expect it uses the locale, which is always a thread can of worms, but the behaviour you're seeing looks more like it's copying parts of the input into statics. To me that's puzzling, but not obviously insane, so maybe I'm just missing some obvious reason why sscanf works better/at all if it's done that way.
Steve Jessop
I note that NSScanner is explicitly defined not to be thread-safe. Could it be that sscanf on iPhone is using a global NSScanner, configured for the locale? I'm going to quit guessing now, ultimately I know approximately this about Macs and iPhones: 0 ;-)
Steve Jessop
A: 

Thank you John and Stephen!

I can confirm that both atof and strtof are safe to use in this situation.

zupamario
Well, you can confirm that they worked this time ;-)
Steve Jessop
Sad but oh so true ;)
zupamario
A: 

If I may ask, why do you need to use sscanf/atof when you can use the NSString numeric conversions?

Kendall Helmstetter Gelner
I decided to go strictliy C for this part of my application for performance reasons.The cocoa version i had before could not be further optimized and the transition to C brought a huge performance gain.
zupamario