简体   繁体   中英

Is this the correct way to return an array of structs from a function?

As the title says (and suggests), I'm new to C and I'm trying to return an arbitrary sized array of structs from a function. I chose to use malloc , as someone on the internet, whose cleverer than me, pointed out that unless I allocate to the heap, the array will be destroyed when points_on_circle finishes executing, and a useless pointer will be returned.

The code I'm presenting used to work, but now I'm calling the function more and more in my code, I'm getting a runtime error ./main: free(): invalid next size (normal): 0x0a00e380 . I'm guessing this is down to my hacked-together implementation of arrays/pointers.

I'm not calling free as of yet, as many of the arrays I'm building will need to persist throughout the life of the program (I will be adding free() calls to the remainder!).

xy* points_on_circle(int amount, float radius)
{
  xy* array = malloc(sizeof(xy) * amount);

  float space = (PI * 2) / amount;

  while (amount-- >= 0) {
    float theta = space * amount;

    array[amount].x = sin(theta) * radius;
    array[amount].y = cos(theta) * radius;
  }

  return array;
}

My ground-breaking xy struct is defined as follows:

typedef struct { float x; float y; } xy;

And an example of how I'm calling the function is as follows:

xy * outer_points = points_on_circle(360, 5.0);
for(;i<360;i++) {
    //outer_points[i].x
    //outer_points[i].y
}

A pointer in the right direction would be appreciated.

Allocating memory in one function and freeing it in another is fraught with peril.

I would allocate the memory and pass it (the memory buffer) to the function with a parameter indicating how many structures are allowed to be written to the buffer.

I've seen APIs where there are two functions, one to get the memory required and then another to actually get the data after the memory has been allocated.

[Edit] Found an example: http://msdn.microsoft.com/en-us/library/ms647005%28VS.85%29.aspx

EDIT: You are returning the array correctly , but ...


Consider you're making an array with 1 element

xy *outer_points = points_on_circle(1, 5.0);

What happens inside the function?
Let's check ...

  xy* array = malloc(sizeof(xy) * amount);

allocate space for 1 element. OK!

  while (amount-- >= 0) {

1 is greater or equal to 0 so the loop executes (and amount gets decreased)
after setting array[0] you return to the top of the loop

  while (amount-- >= 0) {

0 is greater or equal to 0 so the loop executes (and amount gets decreased)
You're now trying to set array[-1] , which is invalid because the index refers to an area outside of the array.

I would say that this program design is fundamentally flawed. First of all, logically a function which is doing calculations has nothing to do with memory allocation, those are two different things. Second, unless the function that allocates memory and the one that frees it belong to the same program module, the design is bad and you will likely get memory leaks. Instead, leave allocation to the caller.

The code also contains various dangerous practice. Avoid using -- and ++ operators as part of complex expressions, it is a very common cause for bugs. Your code looks as if it has a fatal bug and is writing out of bounds on the array, just because you are mixing -- with other operators. There is never any reason to do so in the C language, so don't do it.

Another dangerous practice is the reliance on C's implicit type conversions from ints to float (balancing, aka "the usual arithmetic conversions"). What is "PI" in this code? Is it an int, float or double? The outcome of the code will vary depending on this.

Here is what I propose instead (not tested):

void get_points_on_circle (xy* buffer, size_t items, float radius)
{
  float space = (PI * 2.0f) / items;
  float theta;
  signed int i;

  for(i=items-1; i>=0; i--)
  {
    theta = space * i;

    buffer[i].x = sin(theta) * radius;
    buffer[i].y = cos(theta) * radius;
  }
}

I think this:

while (amount-- >= 0 ) {

should be:

while ( --amount >= 0 ) {

Consider the case where amount is zero initially.

You're doing the right thing as far as I'm concerned, provided of course your callers are free'ing the results.

Some people prefer to have the memory allocation and freeing responsibility in the same place (for symmetry), ie outside your function. In this case you would pass a pre-allocated xy* as a parameter and return the size of the buffer if the pointer was null:

int requiredSpace = points_on_circle(10, 10, NULL);
xy* myArray = (xy*)malloc(requiredSpace);
points_on_circle(10, 10, myArray);
free(myArray);

You are iterating over one element to much.

You should use the goes down to zero operator instead:

while ( amount --> 0) {
    ...
}

Allocating memory in one function and freeing it in the other is like giving a gun to a four year old. You shoudn't do that.

While your decision to count down amount and use while instead of using a temp value saved a few memory cycles, it is more conceptually confusing. Especially in a case where the maths are taking all the time here, you only save fractions.

But that is not the reason why you should waste minor amounts of time. This question is the reason: you've wasted hours! This applies to even the most experienced and smartest programmers. The mistakes are just more complicated and beyond the scope of a stackoverflow answer! In other words, the Peter Principle applies to coding too.

Don't make the mistake as you gain experience that you can get away with taking these kinds of risks to save a cycle or two. That is why McConnell in Code Complete lists Humility as a positive programmer attribute.

Here's the solution you probably thought of to start with:

xy* points_on_circle(int amount, float radius)
{
  xy* array = malloc(sizeof(xy) * amount);

  float space = (PI * 2) / amount;

  int index;

  for (index=0;index<amount;index++) {
    float theta = space * index;

    array[index].x = sin(theta) * radius;
    array[index].y = cos(theta) * radius;
  }

  return array;
}

If you need speed, a tiny thing you can do is put theta outside the loop set to 0 and add 'space' each time since + is bound to be cheaper than * in floating point.

speed it up 10x or more?

If you need serious speed, this tip from answers.com will give you an improvement of 10x if you do it right:

By Pythagoream's theorem, x2 + y2 is radius2. It is then simple to solve for x or y, given the other along with radius. You also do not need to compute for the whole circle - you can compute for one quadrant, and generate the other three quadrants by symmetry. You generation loop would, for example, simply iterate from origin to radius as x by delta x, generating y, and reflecting that in the other three quadrants. You can also compute for one half of a quadrant, and use both symmetry and reflection to generate the other seven half quadrants.

When I was 12 I thought I was hot stuff drawing a circle using sin and cos in graphics 8 on my atari 800. My cousin Marty (when as of late worked form Microsoft robotics) erased my program and implemented the above solution, using only addition in the loop if I remember right, and draw the same circle in 4 seconds instead of a minute! Had I not been baptized I would have bowed down in worship. Too bad I don't have the code handy but I'd bet a little googling would bring it up. Anybody?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM