LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

Why doesn't the debugger follow dynamic memory allocations well?

Here's an example of a code block that doesn't seem to work right with the CVI compiler/debugger, but works fine with other C compilers/debuggers

struct arg_int* arg_intn(const char* shortopts,
                         const char* longopts,
                         const char *datatype,
                         int mincount,
                         int maxcount,
                         const char *glossary)
    {
    size_t nbytes;
    struct arg_int *result;

	/* foolproof things by ensuring maxcount is not less than mincount */
	maxcount = (maxcount<mincount) ? mincount : maxcount;

    nbytes = sizeof(struct arg_int)     /* storage for struct arg_int */
           + maxcount * sizeof(int);    /* storage for ival[maxcount] array */

    result = (struct arg_int*)malloc(nbytes);
    if (result)
        {
        /* init the arg_hdr struct */
        result->hdr.flag      = ARG_HASVALUE;
        result->hdr.shortopts = shortopts;
        result->hdr.longopts  = longopts;
        result->hdr.datatype  = datatype ? datatype : "<int>";
        result->hdr.glossary  = glossary;
        result->hdr.mincount  = mincount;
        result->hdr.maxcount  = maxcount;
        result->hdr.parent    = result;
        result->hdr.resetfn   = (arg_resetfn*)resetfn;
        result->hdr.scanfn    = (arg_scanfn*)scanfn;
        result->hdr.checkfn   = (arg_checkfn*)checkfn;
        result->hdr.errorfn   = (arg_errorfn*)errorfn;

        /* store the ival[maxcount] array immediately after the arg_int struct */
        result->ival  = (int*)(result+1);
        result->count = 0;
        }
    /*printf("arg_intn() returns %p\n",result);*/
    return result;
    }

When I try to dereference this structure's 'ival[0]' the debugger constantly complains of a fatal runtime error and declares it out of the array bounds.

 

This is from the argtable2 open source library available at http://argtable.sourceforge.net/ if you want to try and reproduce it.  I'm using CVI 2010 SP1.

Message 1 of 8
(3,547 Views)

Unfortunately, you have run into one of the inherent limitations of CVI's run-time checking. Even though it is perfectly legal in C and somewhat common practice, our run-time checking doesn't like it when you conceptually split up a block of memory and treat it as two or more separate blocks. 

 

While I cannot fix the problem in our run-time checking without breaking other use cases, I can explain what's causing the error and how you can work around it.

 

When you malloc memory, we assume that the memory block is going to hold one or more instances of the type to which you're casting the memory block. In this case, CVI is treating the new memory block as an array of arg_info structures. But your new memory block is not big enough to hold more than one instance of struct arg_info, so we implicitly truncate the memory block to sizeof(struct arg_info). Because of the implicit truncation, s->values is now pointing past the end of the memory block and any access will result in an error.

 

#include <ansi_c.h>

struct arg_int {
	char some_large_block[64];
	int count;
	int *values;
};

int main (int argc, char *argv[])
{
	int i, n = 4;
	
	struct arg_int *s = malloc(sizeof *s + n * sizeof *s->values);
	s->count = n;
	s->values = (int *)(s+1);
	
	for (i = 0; i < n; ++i)
		s->values[i] = i;
	return 0;
}

 

You can avoid the implicit truncation in the original code by assigning to a (void*) first. This retains the original block size by keeping the actual type of the data in the memory block in limbo. Subsequent casts do not truncate the block. We truncate only when the cast occurs implicitly as part of a malloc. s->values points to the remainder of the block and you can access it freely, but you do not get any run-time checking on it.

 

#include <ansi_c.h>

struct arg_int {
	char some_large_block[64];
	int count;
	int *values;
};

int main (int argc, char *argv[])
{
	int i, n = 4;
	
	struct arg_int *s;
	void *memory = malloc(sizeof *s + n * sizeof *s->values);
	s = memory;
	s->count = n;
	s->values = (int *)(s+1);
	
	for (i = 0; i < n; ++i)
		s->values[i] = i;
	return 0;
}

 

If you want full run-time checking on s->values, you have to allocate the two components separately.

#include <ansi_c.h>

struct arg_int {
	char some_large_block[64];
	int count;
	int *values;
};

int main (int argc, char *argv[])
{
	int i, n = 4;
	
	struct arg_int *s = malloc(sizeof *s);
	s->count = n;
	s->values = malloc(n * sizeof *s->values);
	
	for (i = 0; i < n; ++i)
		s->values[i] = i;
	return 0;
}

 

Another option is to use an incomplete array. An incomplete array is an array of unspecified or zero size at the end of a structure definition. CVI is implicitly growing the array to fill up the rest of the allocated memory block. You do not get run-time checking for the array.

 

#include <ansi_c.h>

struct arg_int {
	char some_large_block[64];
	int count;
	int values[0];
};

int main (int argc, char *argv[])
{
	int i, n = 4;
	
	struct arg_int *s = malloc(sizeof *s + n * sizeof *s->values);
	s->count = n;
	
	for (i = 0; i < n; ++i)
		s->values[i] = i;
	return 0;
}

 

You can also disable run-time checking for your memory block by going through a series of casts: http://zone.ni.com/reference/en-XX/help/370051K-01/cvi/disablinguserprotectionforindividualpointer/

 

Best regards.

 

 

Message 2 of 8
(3,532 Views)

Sorry, I forgot to attach an example for the last option. If you decide to use this solution, use the definition of DISABLE_RUNTIME_CHECKING() below because it works for 32-bit and 64-bit applications.

 

#include <ansi_c.h>

#define DISABLE_RUNTIME_CHECKING(ptr) ((ptr) = (void *) ((intptr_t)(ptr)))

struct arg_int {
	char some_large_block[64];
	int count;
	int *values;
};

int main (int argc, char *argv[])
{
	int i, n = 4;
	
	struct arg_int *s = malloc(sizeof *s + n * sizeof *s->values);
	s->count = n;
	s->values = (int *)(s+1);
	DISABLE_RUNTIME_CHECKING(s->values);
	
	for (i = 0; i < n; ++i)
		s->values[i] = i;
	return 0;
}

 

0 Kudos
Message 3 of 8
(3,529 Views)

Sorry again. DISABLE_RUNTIME_CHECKING should be defined as:

 

#define DISABLE_RUNTIME_CHECKING(ptr) ((ptr) = (void *) ((uintptr_t)(ptr)))

 

0 Kudos
Message 4 of 8
(3,528 Views)

That's a great explaination, and I appreciate the thoroughness (I thought I might not get an immediate answer).  Unfortunately, it's not the answer I hoped for.  The fact that CVI doesn't support this seemingly standard way of using allocated memory hinders the ability to use several common ANSI-C libraries without modification.  Is it the intention to fix this in any future releases?  I'd really hate to have to work implement a workaround in CVI for C code that works in other C compilers we use.

0 Kudos
Message 5 of 8
(3,513 Views)

Unfortunately, I don't know if we can fix this without breaking other use cases and/or losing backwards compatibility because of changes to internal data structures.

 

If you trust your libraries and don't need to or want to debug them, you can disable debugging and run-time checking for them without modifying them. Right-click on a file in the project tree and select 'Enable Obj. Option'. You can also set the option for several files at once ('Toggle .Obj Option'). The selected files will be compiled without debugging and run-time checking. The rest of your project will continue to be debuggable and to have run-time checking.

 

Another possibility is to turn of run-time checking completely in your application by lowering the debugging level to 'No run-time checking'. You can debug all of your application, but you won't get any run-time checking anymore.

0 Kudos
Message 6 of 8
(3,506 Views)

That seems like a better workaround.  Not perfect, but better.

 

Can I request that it be looked at and that the CVI team try to fix it?  It'd be nice to be able to compile/debug any ANSI-C code in CVI without worrying about compatibility.  From the programmer's perspective, this style of dynamic allocation (using one malloc instead of two) is preferred because it's less clean up in the end and quicker to execute if there's a lot of dynamic allocation happening.

0 Kudos
Message 7 of 8
(3,503 Views)

I have filed a bug report on the issue. I don't know if we can fix it, but we can certainly look into it during this or the next release cycle. Thank you for pointing out the problem!

0 Kudos
Message 8 of 8
(3,486 Views)