Search code examples
c++iosxcodensdatastdvector

Storing and Restoring std::vector data


I am able to store and restore std::vector data to NSData and back, however, it works intermittently. I had originally posted the question and accepted the answer because it seemed to work here:

Storing and Restoring std::vector from NSData

However, sometimes it works and other times it causes EXC_BAD_ACCESS errors. I created an example Xcode project that reproduces the issue that may be downloaded from here:

http://3DTOPO.com/VectorToData.zip

Sometimes it will crash at test iteration 33, 47 or 105 for instance.

Here is the backtrace for when it does crash:

* thread #1: tid = 0x1542ee1, 0x000000010d45e65e libsystem_platform.dylib`_platform_memmove$VARIANT$Nehalem + 254, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x7faf29000000)
frame #0: 0x000000010d45e65e libsystem_platform.dylib`_platform_memmove$VARIANT$Nehalem + 254
frame #1: 0x0000000109f46753 Foundation`_NSDataCreateVMDispatchData + 131
frame #2: 0x0000000109f44cf2 Foundation`-[_NSPlaceholderData initWithBytes:length:copy:deallocator:] + 230
frame #3: 0x0000000109f50902 Foundation`-[NSData(NSData) initWithBytes:length:] + 37
* frame #4: 0x0000000109ea2bfe VectorToData`storeContourData() + 574 at ViewController.mm:86
frame #5: 0x0000000109ea2943 VectorToData`-[ViewController viewDidLoad](self=0x00007faf3a10f410, _cmd="viewDidLoad") + 1443 at ViewController.mm:67
frame #6: 0x000000010af31984 UIKit`-[UIViewController loadViewIfRequired] + 1198
frame #7: 0x000000010af31cd3 UIKit`-[UIViewController view] + 27
frame #8: 0x000000010ae07fb4 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61
frame #9: 0x000000010ae0869d UIKit`-[UIWindow _setHidden:forced:] + 282
frame #10: 0x000000010ae1a180 UIKit`-[UIWindow makeKeyAndVisible] + 42
frame #11: 0x000000010ad8eed9 UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131
frame #12: 0x000000010ad95568 UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1769
frame #13: 0x000000010ad92714 UIKit`-[UIApplication workspaceDidEndTransaction:] + 188
frame #14: 0x000000010d78a8c8 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
frame #15: 0x000000010d78a741 FrontBoardServices`-[FBSSerialQueue _performNext] + 178
frame #16: 0x000000010d78aaca FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45
frame #17: 0x000000010a905301 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #18: 0x000000010a8fb22c CoreFoundation`__CFRunLoopDoSources0 + 556
frame #19: 0x000000010a8fa6e3 CoreFoundation`__CFRunLoopRun + 867
frame #20: 0x000000010a8fa0f8 CoreFoundation`CFRunLoopRunSpecific + 488
frame #21: 0x000000010ad91f21 UIKit`-[UIApplication _run] + 402
frame #22: 0x000000010ad96f09 UIKit`UIApplicationMain + 171
frame #23: 0x0000000109ea237f VectorToData`main(argc=1, argv=0x00007fff55d5d6e0) + 111 at main.m:14
frame #24: 0x000000010d12a92d libdyld.dylib`start + 1

Although working with NSData is preferred, I would be willing to use a C++ solution, but the examples (e.g. fstream and iostream) I have found I have not been able to get to work on iOS. If a C++ solution is presented, please show how to restore the data as well as storing it.

In case you can’t open the project linked above, here is the pertinent code, just drop name it ViewController.mm and drop it in a new Single View Application Xcode project:

//
//  ViewController.m
//  VectorToData
//
//  Created by jeshua on 8/27/16.
//  Copyright © 2016 3DTOPO Inc. All rights reserved.
//

#import "ViewController.h"
#import <vector>

using namespace std;

typedef vector<CGPoint> CGContour;
typedef vector<CGContour> CGContours;
const size_t cgContourSize = sizeof(CGContour);
static CGContours contoursVector;

@interface ViewController ()

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];


    for (int testNumber = 0; testNumber < 1000; testNumber++) {

        NSLog(@"__________ Test Number: %d __________", testNumber);

        long numberOfContours = arc4random_uniform(2000) + 1000;

        contoursVector.clear();

        contoursVector = CGContours(numberOfContours);

        long numberOfBytesGenerated = 0;

        for( int contourNum = 0; (contourNum < numberOfContours); contourNum++ ) {

            long contourPointCount = arc4random_uniform(5000) + 1;

            CGContour thisContour = CGContour(contourPointCount);

            for( long pointNum = 0; pointNum < thisContour.size(); pointNum++ )
            {
                thisContour[pointNum] = CGPointMake(arc4random_uniform(800), arc4random_uniform(1200));
            }

            //This might seem redundant, but in my actual code the data is converted to CGPoints here
            //and is not already a CGPoint
            contoursVector[contourNum] = CGContour(contourPointCount);
            for( long pointNum = 0; pointNum < thisContour.size(); pointNum++ )
            {
                contoursVector[contourNum][pointNum] = thisContour[pointNum];
                numberOfBytesGenerated += cgContourSize;
            }

        }

        NSLog(@"  numberContoursGenerated: %lu, numberOfBytesGenerated: %lu", numberOfContours, numberOfBytesGenerated);

        storeContourData();

        NSLog(@"\n\n");
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

void storeContourData() {

    NSMutableArray<NSData *> *contourDataArray = [NSMutableArray new];

    long numberContoursStored = 0;
    long numberOfBytesStored = 0;

    for(const CGContour &contour : contoursVector)
    {

        // Both these data initializations seem to work the same
        NSData *data = [[NSData alloc] initWithBytes:&contour[0]
                                              length:contour.size() * cgContourSize];

//      NSData *data = [[NSData alloc] initWithBytes:contour.data()
//                                            length:contour.size() * cgContourSize];

        [contourDataArray addObject:data];

        numberContoursStored++;
        numberOfBytesStored += contour.size() * cgContourSize;
    }

    NSLog(@"  numberContoursStored:    %lu, numberOfBytesStored:    %lu", numberContoursStored, numberOfBytesStored);
}

@end

Solution

  • The problem is wrong calculation of contour byte size, it's not

    contour.size() * cgContourSize; // sizeof(vector<CGPoint>)
    

    but should be

    contour.size() * sizeof(CGPoint);
    

    You cannot calculate byte size of a vector (or other data container) up-front.

    Statements like this should be avoided:

    sizeof(vector<...>);
    

    Because the result is the byte size of the vector object, not the byte size of data in the vector.