Pointers Self-Congratulation


#1

In Other News, I successfully used C pointers as function arguments for the first time yesterday.
Can I have a medal?

:stuck_out_tongue:

a|x


#2

Same, building sysex strings.


#3

Excellent!
Rather you than me :wink:

a|x


#4

It's all good, I'm happily talking to my Launchpad Pro now and saving myself a lot of MIDI messages :slight_smile:


#5

That's cool. I have a Launchpad Mini. Wonder if I'll be able to use that, too.

a|x


#6

Was it by design or were you just randomly adding *s and &s?

Because that's how I started :slight_smile:

But it's cool when it clicks. Well done.


#7

Not random. I needed a functions that was able to write to different variables depending on where it was called from, so pointers seemed like a good way to achieve that. I'm sure I'll be using them again in the future :slight_smile:

I did a basic course in C many years ago, and just remember people saying pointers were confusing, so I was surprised it seemed relatively easy.

a|x


#8

Just keep in mind that there is no protection against misbehavior with pointers, like writing outside the boundaries of an array. Even casting an integer to a pointer allows you to write literally anywhere in memory with unpredictable results.


#9

Point taken, Johannes. I guess it's perfectly possible to overwrite parts of the firmware. I will use with caution..

Here's the function, in case it's of use to anyone. It's a slew/glide effect, with the state being stored in a variable referenced by a pointer argument passed in to the function.:

// Slew function, with pointer to state variable
__attribute__ ( ( always_inline ) ) __STATIC_INLINE int32_t slew(int32_t target, int32_t *state, int32_t slewTime, bool enable) {
	if (enable) {
		*state = ___SMMLA(*state - target, (-1 << 26) + (slewTime >> 1), *state);
		return *state;
	} else {
		*state = target;
		return target;
	};
};

The actual slew/lag line is copied from the Factory Glide object.

a|x


#10

I should probably change the conditional to

if (enable && slewTime > 0) {
}

a|x


#11

Well, overwriting the firmware in flash does not happen accidentally (that takes more than a simple memory write), only to volatile memory, which is fully restored by resetting the board. If you really accidentally manage to write to flash you'll get a CRC mismatch notification when you connect to the board.
So it is fairly harmless, but you could observe weird - seemingly unrelated - effects when objects/patches go out of bounds, and those errors could even persist until you reboot the board.
My message is really: don't be scared, but if objects behave inconsistently, perhaps check your pointer math.
Nothing wrong with your example.


#12

I'm wondering if I'm doing something stupid here. My slew function with pointers does work, but I'm wondering if I'm creating pointer variables where I don't need to.

I have a set of variables to store the slew states. Then I created pointer variables to pass in to the function.

// Slew function, with pointer to state variable
int32_t slew(int32_t target, int32_t *state, int32_t slewTime, bool enable) {
	if (enable && slewTime > 0) {
		*state = ___SMMLA(*state - target, (-1 << 26) + (slewTime >> 1), *state);
		return *state;
	} else {
		*state = target;
		return target;
	};
};
    
// State variable
int32_t slewState0;
// Pointer to above
int32_t *slewState0Ptr = null;

slewedVar = slew(slewedVar, slewStatePtr, slewTime, true);

I'm now wondering if I could eliminate my pointer variables, somehow.

Would this work, instead, I wonder?

// Slew function, with pointer to state variable
int32_t slew(int32_t target, int32_t *state, int32_t slewTime, bool enable) {
    	if (enable && slewTime > 0) {
    		*state = ___SMMLA(*state - target, (-1 << 26) + (slewTime >> 1), *state);
    		return *state;
    	} else {
    		*state = target;
    		return target;
    	};
    };
        
    // State variable
    int32_t slewState0;

    slewedVar = slew(slewedVar, &slewState, slewTime, true);

Sorry for the stupid question...

a|x


#13

There are no stupid questions, only stupid answers...

Your second example is perfectly valid. You don't need to create the pointer slewState0Ptr, as far as your example goes, as you have direct access to the variable itself. The compiler effectvely creates the pointer for you as the function is called.
If you didn't have direct access to the slewState0 variable for some reason, but you did have the pointer to it, then you would just use the pointer as you did in the first case.

The other reason for creating a pointer to pass to a function is when you want to pass a pointer to a pointer. That lets you change what the original pointer points to, within the function. Only use that when you need to though, pointers are confusing enough when you first learn about them. :slight_smile:


#14

Cool, thanks of that. I think I'll leave pointers to pointers for the moment.. baby steps...

a|x


#15

Do you know if it's possible to set pointers to inlet/outlet ports, in an Axo object?

int32_t *outPtr = &outlet_out;

And possible keep these in an array? I'm thinking of interesting ways to cross-connect a series of in and outlets, and being able to store pointers to both in arrays would be handy, I think.

a|x


#16

Storing a pointer to a local variable gives undefined behavior after the local variable goes out of scope. The compiler will not give an error but it will cause trouble...

I'm not sure what your use case is?


#17

I was investigating the possibility of keeping arrays of pointers to, say, 12 inlets and 12 outlets.

I could then loop through the outlets array, setting the values of the outlets (via the pointers pointing to them), to the values of the corresponding inlets (via their pointers), selected by their matching array indices.

The fun would come if you rotated or randomised one of the pointer arrays, which would change which inlet was connected to which outlet.

I realise there are other ways to achieve this, without pointers, but I just like the idea of doing it this way, because I've been working with functional coding for a long time, and love using loops :wink:

The other reason is that whenever I see lots of lines of code doing more-or-less the same thing repeatedly, I think "that would be much more elegant and compact as a 'for' loop..".

I guess because I'm not a properly-trained programmer, I have a tendency to fixate on how my code looks.

a|x


#18

This is what I'm doing currently, which seems to work.

int32_t ins[12] = {
	inlet_synthEnergy,
	inlet_synthPeriod,
	inlet_synthK1,
	inlet_synthK2,
	inlet_synthK3,
	inlet_synthK4,
	inlet_synthK5,
	inlet_synthK6,
	inlet_synthK7,
	inlet_synthK8,
	inlet_synthK9,
	inlet_synthK10
};

bend = (inlet_bend || param_bend) ?
   range_pos(__USAT(param_bendIndex + inlet_bendIndex, 27), 0, 46) : 0;

outlet_synthEnergy = scaleValue(ins[indices[bend][0]],  bits[indices[bend][0]],  8);
outlet_synthPeriod = scaleValue(ins[indices[bend][1]],  bits[indices[bend][1]],  8);
outlet_synthK1     = scaleValue(ins[indices[bend][2]],  bits[indices[bend][2]], 16);
outlet_synthK2     = scaleValue(ins[indices[bend][3]],  bits[indices[bend][3]], 16);
outlet_synthK3     = scaleValue(ins[indices[bend][4]],  bits[indices[bend][4]],  8);  
outlet_synthK4     = scaleValue(ins[indices[bend][5]],  bits[indices[bend][5]],  8);  
outlet_synthK5     = scaleValue(ins[indices[bend][6]],  bits[indices[bend][6]],  8);
outlet_synthK6     = scaleValue(ins[indices[bend][7]],  bits[indices[bend][7]],  8);
outlet_synthK7     = scaleValue(ins[indices[bend][8]],  bits[indices[bend][8]],  8); 
outlet_synthK8     = scaleValue(ins[indices[bend][9]],  bits[indices[bend][9]],  8);
outlet_synthK9     = scaleValue(ins[indices[bend][10]], bits[indices[bend][10]], 8);
outlet_synthK10    = scaleValue(ins[indices[bend][11]], bits[indices[bend][11]], 8);

'bend' then controls the index into a constant array, where each array member contains the order of the values from the 'ins' array which are assigned to each outlet.

// Indices for transforming connections between inlet and outlet
const uint8_t indices[47][12] = {
	{0,1,2,3,4,5,6,7,8,9,10,11},
	{0,1,2,3,4,5,6,7,8,9,10,11},
	{0,1,11,2,3,4,5,6,7,8,9,10},
	{0,1,10,11,2,3,4,5,6,7,8,9},
	{0,1,9,10,11,2,3,4,5,6,7,8},
	{0,1,8,9,10,11,2,3,4,5,6,7},
	{0,1,7,8,9,10,11,2,3,4,5,6},
	{0,1,6,7,8,9,10,11,2,3,4,5},
	{0,1,5,6,7,8,9,10,11,2,3,4},
	{0,1,4,5,6,7,8,9,10,11,2,3},
	{0,1,3,4,5,6,7,8,9,10,11,2},
        etc.
}

and 'bits' is an array holding the number of bits used for the values of each outlet, so values can be scaled if needed, with the 'scaleValue' function doing that when inlet and outlet bits don't match.

I still think it could be more elegantly achieved with pointer arrays, though.

a|x