Embedded - 10: Hands Off, Baby
Episode Date: July 17, 2013Jen Costillo (@r0b0ts0nf1r3) joins Elecia White to discuss the secret parts of C, keywords that only embedded software engineers seem to know about. They talk about interviewing and why these keywords... make good questions for finding folks who use the language to its full potential. On the show they mention a list of embedded interview questions with answers. (Note: Elecia's book has many excellent interview questions and what interviewers look for when they ask them.) Producer Christopher White sends along a more concise introduction to the often unused register keyword.Â
Transcript
Discussion (0)
Hello, I'm Eliseo White.
Welcome to Making Invented Systems.
If you love gadgets, especially making them, you're in the right place.
Today we're going to talk about the important parts of the language that many programmers never really understand.
Happily, Jen Castillo is back to share her wisdom with us.
Hello, it's good to be back. It's been a busy couple of weeks, so...
How have you been, Jen?
Well, like I said, very, very busy.
I've just given notice at my current job, my current very safe, warm, huggy-type place,
and I'm going to go out into the cold, unyielding world of startups.
Unyielding world of startups, but that means you get a better title, right?
Damn straight.
But you emailed me to say you want to talk about explosions, right?
Okay, okay, now she's laughing because that was all about volatile.
We're going to talk about C and C++, though the concepts happen in other
languages, and even Java has a volatile keyword. It means mostly the same thing. For the most part,
it's going to be C and C++ and volatile. According to Google, the definition of the word means
easily evaporated at normal temperatures,
and the synonyms are changeable, fixable, and inconstant, which actually makes a lot
of sense for the keyword in programming languages.
Was there no alternate definition like sublimate or something like that?
Because it also sounds like sublimate.
But anyway.
And, well, there were a couple other definitions involving fire and explosions, which was how we got started on explosions.
How I got started on explosions.
What does volatile mean to you, Jen?
Great. Today's going to be a giggle fest.
It means everything to me.
It's probably the most common keyword I use in my embedded firmware coding practice.
Well, I mean, other than void and int and all.
Well, okay, so those are other keywords.
But in terms of, it's the fact that we use volatile is so central to how we can even
declare ourselves as firmware engineers.
It's something that is often asked in firmware engineering interviews. is so central to how we can even declare ourselves as firmware engineers.
It's something that is often asked in firmware engineering interviews.
What does volatile mean and how do you use it?
I certainly have asked it myself and I've been asked it more times than I can count.
You ask it too, don't you? Of course I ask that because if they don't know how to use it, what it's used for.
What do you look for in an answer?
Okay.
I look for effectively three answers.
One, the first thing is, do they know what it means?
Usually people understand that, hey, it's to denote to the compiler that this should always be checked.
Do not assume whatever the last value is is correct.
And do not optimize out the last value is correct and do not optimize
out reading this value whenever possible. Because it might change somewhere else in the code.
Right. And most firmware engineers who get that far, they also understand that, you know,
that the reason why this is, is because they're using this in a hardware register, such as,
you know, that serial port you're trying to get in the data. You're waiting for this bit to go high.
The status register.
Right.
And you don't want to optimize that out.
And so they always go, after that, they always know, hey, I used it when I was writing a
UART driver or something similar, something hardware register centric.
But that's not all that it does.
No, no. hardware register centric but that's not all that it does no no because if a variable changes in an
interrupt that could also uh that should also be marked volatile a global variable changes in an
interrupt because it may you may have while waiting for serial to be done and waiting for
serial to be done as a variable that's changed in your serial driver, then you need to mark that
volatile.
Otherwise, the compiler is going to go, yeah, this never changes because in this loop, you
don't change it.
And so it needs to know not to optimize it out, not to put it in a regular processor
register.
So let's talk about one example of what that would mean.
So you've already used UART.
What's common is you keep reading in data
from your ISR, from your UART, and then you're taking that data and you're putting it into a
circular buffer. Well, you need to update the pointer to that circular buffer. You need to
push that data into the circular buffer. That way, your main task loop can look through that data
and use it however it needs to. And so this is a simple, the UART's putting data into the circular buffer
and your main loop is reading it out.
Right.
So first thing you're going to want, obviously,
volatile for all those hardware registers,
but also for the circular buffer, the index where it's pointing,
you also need to make that volatile.
So in addition to the UART registers, the hardware ones,
the index into the circular buffer,
and does the circular buffer itself need to be volatile?
I would say generally I would make that the case.
Because it's better safe than sorry on this one.
Yeah.
You might as well make the compiler not
optimize these variables out and you maybe lose just a little smidgen of cpu processing cycles
in an effort not to make it make them wrong i mean that's the problem so let's let's say
you needed a volatile variable and you didn't make it so and then you uh so i had the the is the
you are ready instead let's let's talk about the circular buffer let's say you didn't have the
index be volatile yep and you're going through and you're reading stuff out and this is in your isr
is this in your task loop in In my task loop. Okay.
And so I'm at the end and now I'm going to wait for some signal that says the buffer's full again. And then I get an interrupt and it puts 10 bytes into the buffer.
And so my loop starts going and then it reads out how many are in, but it keeps reading out how many are in there.
And the compiler just eliminates that while there is stuff left over because it doesn't know that anything can change outside the loop.
Right. The code was written in such a way.
So when you have the assembly, when you look at the actual listing file, all're going to see is like hey we already know what this value is we already set that that that
index up here last time we read it was all good no one else is touching this we're gangbusters
exactly uh and so most of the keywords we're going to talk about today
have to do with telling the compiler something special is happening.
Even when you have like int and character array or double or float,
that's still all about telling the compiler what it was you meant
when you said I want a variable.
It's like adjectives in English.
It's expressing what you meant and trying to be as clear as possible.
Oh, I like variable descriptions as adjectives.
I like that.
It helps me cast things.
I don't.
I hate English.
So there is also a register keyword.
Well, before we go on to that, actually, I want to follow up with an additional. So we
talked about two ways to use volatile. Oh, I thought we hit the third with in variables. Okay,
so we have registers and variables. What else? Registers and variables. But and we talked about
in regards to when you're in your ISR. Well, let's, you know, extend that a little bit. And
remember that, hey, maybe we're not writing ISRs,
but anytime that we have, let's say, two tasks
and they share information,
that information also needs to be volatile.
Especially if you're working with a lightweight
real-time operating system.
Correct.
Hard toss.
Then you may not have the big signaling
and semaphore systems you get with a with a i want to say
a real operating system that's not correct so if you if you're if you're basically have your own
effective system or art house that you've developed um you know it could it could be you
know it could just be you know time sharing um or as a single task loop and round robin between them.
But at the end of the day, those variables are being shared.
And so especially if you still have an interrupt or something like that,
you need to make sure that everything's always checked.
Yeah, that makes sense.
I'd forgotten about that.
I don't usually like RTOSs.
It's a bigger, you know, for most people who are thinking about it this way,
when I ask this question and that's the answer that they give me,
usually these are people who are writing Linux drivers
or more used to working within the kernel.
That makes sense.
And then, so that's what you look for with volatile.
It's a little tough asking that as an interview question
because it is pure knowledge.
It isn't how are they
thinking? It's do they actually know what they're doing? Yeah. Yeah. And it really does tell you a
lot about what level of experience that they've had and depth of experience that they've had.
And sometimes you'll end up with a really good story about a bug they were tracking down.
Yes. Those are the good interviews. The ones that it's not only here's what I know,
but here's how I've used it.
Yeah, or didn't use it and found out I should have.
Right.
Why don't we put volatile on everything?
Jen's looking at me like, because we don't.
That's the stupidest question I've ever heard.
I don't need to read everything.
Like, for example, if I have a local variable in a function.
Int i.
Exactly.
That's exactly what I was thinking.
Maybe I don't need to do that.
It's not going to be, you know, no one's going to come and swoop in and change that value or certainly shouldn't.
It's a way of protecting us against someone swooping in and changing things on us.
Well, it's more than that. If you have int i and you're doing like a four i equals zero to
a hundred loop, your i probably, your variable i is not going to be in memory. It's not going
to put it in RAM and RAM has some time to access. Maybe it's only one or two clock cycles.
Maybe it's no clock cycles and it's free.
But a lot of times RAM is a little slower than using a processor register.
So your variable I is going to go into a processor register like R0.
And then you'll be able, it will go much, much faster.
And we want the optimizer to work. We want the optimizer to work we want the optimizer to
eliminate i if it doesn't have to do i plus plus and then i minus minus and then i plus plus it
can just reorder things a little bit we want it to go ahead and and optimize out what it can
optimizers are fantastic but we need to make sure we we know what can and can't be optimized and what should go to
a memory location all the time you said the reason to use volatile for hardware registers yeah that's
because you never you are status register is never ever going to be a register that can be randomly selected. It's never going to be R31.
It always has to go to a hardware address location.
And so that's why that has to be volatile.
And that's why you don't want everything to be volatile is because if you can use those
registers, you want to.
It'd be so slow if you didn't, if you couldn't use, if you put volatile in front of everything
and you didn't use those registers.
But what you have done, Elle,
is set us up to talk about register,
which we've pretty much already talked about in your example.
Well, and that's kind of register.
Nobody ever uses that keyword anymore.
Yeah, I can't think of the last time I used it,
but I know I've used it.
I think the last time I used it was type def or pound define register to volatile.
I wouldn't normally do that.
Oh, I did that backwards.
I want to replace all of the register keywords
with the volatile keyword.
So that would be pound define.
It's hard to write code if you aren't typing.
Pound define register to volatile.
And that would replace all of my register keywords with volatile.
Sometimes it's nice to know which way you're using the volatile keyword.
Are you using it because it is a hardware register or because it is a shared variable?
And I don't usually do that now.
It's all just volatile. And I let the person who comes after me swim shared variable. Right. And I don't usually do that now. It's all just volatile.
And I let the person who comes after me swim for themselves.
Right.
The only time I can think of that I maybe should have used register
or I was pretty much effectively using register,
but we didn't have a C-Stack setup,
so I had to do everything with all the available registers
to determine the size of RAM
and basically the rows and columns to figure out that setup.
That would have been nice, but I didn't have a C-stack to do that.
Anyway.
I remember using it when I was programming on 8051s,
and the compiler wasn't smart enough.
We had a very, very old version of the Kyle compiler,
and it didn't know when its default was to not put variables in registers
because it only had like six or some stupidly small number.
And so it would usually read the variables out of memory,
and I wanted it to be able to use the fast register accesses
so that was one one where we really did put register and what that meant was
please put this variable into this register
uh i'm not even sure registers are properly c99 supported keyword anymore
i haven't like i said i haven't tried i haven't, like I said, I haven't tried,
I haven't found a reason to use it in such a long time,
but I'm sure if you're trying to optimize
in the case that you mentioned earlier,
I can totally see that.
So let's see.
What else is on our list of keywords?
Const.
Oh, our producer really wanted to talk about
const versus const star versus const star const. What else is on our list of keywords? Const. Oh, our producer really wanted to talk about const
versus const star versus const star const.
I don't want to talk about that to you.
I know we have an obligation to the public
to talk about const.
One thing I could...
So there's... So there's one thing I was so there's so there's
there's one thing I was going to say
you know there's a difference between
const, int, star
blah and
no no let's go back
what does const mean at all
I'm going to pretend like I'm a new graduate
I'm going to be gonna I'm gonna pretend like I'm a new graduate I'm gonna be like const
means constant yes indeed const means constant oh man I have heard that answer and actually
you know I have some suggestions for the audience who you know if you really want to read up on that
I believe that Dan Sachs has an article about this. In addition, the Embedded Boot Camp, there's this really great article.
It's 0x10 embedded questions, embedded interview questions.
And one of those questions is the const.
Like, what does this one mean?
What does that mean? And they do a pretty
reasonable treatment, albeit it's very compact and dense in information.
But what I was going to say is, yes, cons doesn't just
read mean only. But once you assign a value to that variable,
it's not going to change. And yet, it does get
memory allocated for it by the compiler.
So it can be passed around as a variable,
unlike if you did a pound define of the same information.
For example, if you did const my number of dogs
and it was equal to 2,
but if you did pound define number of dogs equal to they can
they can't be used in the same way even though that information is effectively the same also
because it does allocate memory for it you can have a pointer to it that can be used later
and say you have a const to a string um you know my my name is Elysia, then if you had pound defined that string,
every place where you used the pound define, it would put that in memory, where if you use a const,
it's a variable, it's used once, there's only one memory allocation for it. And you can't change it
because it's probably allocated to read-only memory,
but at least it isn't in your code 97 times and your code space doesn't bloat.
So const is really useful for that.
In addition, if you wanted to have a const pointer
as a way to tell the programmer,
such that they don't need to go into the linker map, where the offset of a big piece of, a big chunk of data is like, this is where we're keeping the application code.
That's an excellent way to do that.
So that would be a const variable that might actually be set by the linker. I want to talk a little bit about pragmas, and so
let's skip to that. Most people know about pragma pack,
but let's explain anyway.
Okay. You can pack variables
to usually structure variables so that
they take up as little space as possible. For example, if you had a structure that was a Uint8, a Uint16, a Uint32, and then a Uint8 again,
the compiler can reorder those and put them however they want
and make it so that if it's a 32-bit processor, it accesses each one on 32-bit boundaries,
which makes it a little bit faster for processing.
But if you were transferring that memory to something else,
to an EEPROM or across a serial port as just a string of bytes,
if it has reordered them or switched around their sizes
to make it more efficient for itself,
then now the other side isn't going to get it.
Or it's not going to be aligned.
The data is not going to come out the way you expect.
And so pragma pack tells it how it needs to be aligned.
It might need to be aligned on byte boundaries.
It might need to be aligned on 2-bit byte boundaries.
It just depends on what you put there.
That said, if you use, if you, if you use PAC and things aren't aligned quite the way you expect,
you might end up with, what's the word, big gaps in your, in your system, or, or it might reorder.
For example, let's say you're going to send out, you have this protocol,
and you decide to put in a structure,
and let's say for the structure that you have,
it's a little out of order,
so that 32-bit word doesn't quite fall
on a nice four-byte boundary.
It might scoot it up and around,
or it might not be scooted around,
and they might put... Oh, man.
Yeah, no, I see where you're going.
Given what I said,
the start of a 32-bit variable
starts on the third byte of the packet
if we're in pragma pack one.
And so that's kind of bad.
That's not easy for the compiler
or the processor to access.
It has to do shifts and masks and all that.
And we can hide behind C and it'll do it for us.
But it is a little inefficient.
On the other hand, if we didn't have pragma packing,
it was the default pragma packer, the default packing for a 32-bit processor,
then that first Uint is going to be 32 bits,
the U16 is going to be 32 bits,
the 32 bits is going to be 32 bits,
and the U8 is going to be 32 bits.
And so now we've used just a ton more memory.
And if we try to transfer that to a 16-bit processor
with the same structure,
it's going to do something different, and that's bad.
Particularly if you're going to use a mem copy to copy those bytes into the outgoing hardware
peripheral, you'll end up with some spaces or you'll end up with something, something else that
you that you did not intend. So I would say use pack cautiously, make sure that any protocols that
you design kind of have packing in mind. So don't have to deal with this this type of
thought exercise yes um pack is very good for making things small in ram but it's very bad
for making things efficient yeah well there's there's that triangle you know a lot of things
come with a cost triangle and on on one corner of the triangle,
there should be the amount of RAM it takes.
On another, it should be the code size.
And then on the third one, it should be processing power.
And so if you want something that is efficient as possible,
you have to choose what you mean by efficient.
And you could have something be low RAM and low code space,
but the chances are it's
going to be high processing power. And if you want something to be super, super, super efficient for
processing power, then you should throw some RAM and some code space in there. Absolutely.
That's just kind of, you know, how optimizations work in general. There are some easy ways to
fake that out. And I always think that using the inborn compiler optimizer is really useful.
But you should also make sure that you specify what kind of optimization because there's
usually an option for the optimizer for space.
Or cycles or RAM, yeah.
The optimizer needs to know the same way we do
I had lunch with a programmer who said that
optimizations were awful and turned them off
on every opportunity and I stopped
I actually thought that was a joke
I can't imagine that, sure it's a little harder to debug
and sure you have to understand what the processor is doing
what the compiler is doing but if you ever want your device's batteries to last longer,
crank up the optimization, start learning how it works.
Well, so what was there? Naturally, you asked them, why, why, why are you so against optimizing?
Because it was difficult to debug in the compiler or the debugger would jump around and
the variables wouldn't be visible in the debugger.
That was because those variables had been optimized out.
They'd put into registers.
Right.
And yes, that is true.
If you optimize,
especially if you have it on a very high level of optimizing,
like level two or I think,
did they ever go up to three?
I don't think so.
Oh, let's crank this baby all the way to 11.
I'm going to optimize it out of existence. I'm going to go back to work right now
and I'm going to go see whether I can crank it up to 11
and see what happens.
I'm sure none of the variables will make any sense.
But if you're one of those people
who is really uncomfortable with the optimizer,
who thinks it's not worth your time to do this,
there's a couple of things you can do.
One, you can selectively, you know, during
your debug, just say, hey, just put a use pragma to stop optimization around that particular
function. There's usually that if you check your compiler options. In addition, you can always turn
on your listings when you compile. And what it'll do is it'll interweave the C code and the assembly.
And this is excellent practice for anyone
who ever thinks they'll have to write assembly.
Even for those of you who don't,
I love the listing functions.
I love the listing functions too
because I get a chance to sit down
without the stress of having the debugger on and going
and someone looking over my shoulder
wondering why this bug is still there.
And I sit down, I go, okay, this register is doing this.
That's what it, you know, it's now I.
This one, this register over here is my Metallica rules variable.
And this is how they're interacting.
This is how it's going to loop.
Oh, and here's where it's optimizing.
Being able to sit down and print out a function as god awful and ugly as it looks in assembly
is incredibly useful for you to to
become more comfortable with optimizing and understanding that optimizer's little quirks
understanding your compiler's quirks and your optimizers right i totally agree and the few
times that i still write assembly usually what i do is i write what i want and see and then i read
the assembly and then i tweak my c so that it's a little I write what I want in C, and then I read the assembly,
and then I tweak my C so that it's a little closer to what I want in assembly. And then I
compile and check the list and read the assembly again until I'm pretty sure I'm as far as I can
go with the compiler. And then if I still can find a couple more ways to make it just a little
faster, a little better, I can do that. But I don't start with a blank page and just whip out
assembly. No, that's, that's, I think I do a similar version, which is I write the C version
and then I interweave the assembly in between. So it's easier for myself and future coders of
America to read it later on. So that's, that's another keyword. Um, and that's very compiler
dependent, but almost all of the embedded compilers have it. The assembly? Yeah. Yes. Usually it's ASM and then a parentheses and
double quotes, and then your assembly goes in there and double quotes and parentheses.
And it's usually a single line. Yeah, but you don't have to. I've seen it slashed out,
backslash, so that it can go multiple lines, but I think single line looks
better. Yeah, I think single line is much easier to read. But this is one of those things that you
need to go back into your compiler user's guide. So pragma, all the pragma items in there, all the
intrinsics, like if there's a memory copy or a memory access that's specific to your particular
processor, it should be in this
compiler user's guide well disable and enable interrupts is often an intrinsic and a single
command that is in at least the cortex m's that i've been using oh yeah have that and i you know
i was writing my own disable function and then i grepped the example code and look there it is and look it's written
much better than I had and look it's already kind of done for me so it's worth getting to know that
yeah so I actually I was going to tell you one of my little tricks from a long time ago was to use
the assembly keyword or intrinsic to write in my own software breakpoints if that that was already
this yes if it was already part of the assembly instruction set so i believe arms have it
nips has it um i believe there's some version of it in power pc the ti processors i've used it
all have had it right and so this this be really useful, especially if your system doesn't have, your debugger doesn't have a lot of fancy features or has a limit on how many debug breakpoints you can have.
Many processors, you can only have two or with the RMMs, I think it's four hard breakpoints.
Yes.
And that's because they will only support hardware breakpoints.
So this is an easy way.
If you're at a company or you're doing your own thing
and you can't spend several thousand dollars
to get a full-featured debugger,
doing those hardware breakpoints or writing the assembly code
to put in those hardware breakpoints or soft breakpoints
can be really
useful for you to just go, hey, we got to this point.
We're looping here.
We can't go past it until I effectively change the program counter to the next instruction.
Well, and we used to do that for asserts all the time.
We'd go ahead and put asserts into our code and you we'd put in the
uh i think it's asm and then you you quote it and it's bkpt0 yeah and uh and then it would stop
if the variable was out of range and yet if we went to, we could even leave that in because if you don't have a debugger attached, it goes on.
Yeah.
Yeah.
So I think on MIPS it's SDBP.
I keep thinking on PowerPC it's EIEIO, but I think that's wrong.
It is.
It's something like that.
I remember it was pretty funny.
Yeah.
Yeah.
Everyone thinks that's really funny. But yeah, these are the little things that can really make you look really good when it's looking down on your project.
Oh, yeah.
And having a breakpoint that you can just put in software is really, really nice.
Because then you don't have to, you know, you can even have if this variable equals one, two, three, and then that is your error condition.
And now you don't have to try to fake your error condition anymore.
You just wait for it to happen and poof, you have it.
I totally agree with that.
I noticed one thing in our discussion that we didn't do.
And I'm sure that's, you know, either the people are still listening, begging for us to talk about this one keyword. But we didn't do. And I'm sure that's, you know, either the people are still listening,
begging for us to talk about this one keyword,
but we didn't.
We never finished a const,
so I'm not sure they're really expecting much at this point.
You know what we didn't talk about?
Static.
Yeah.
Oh, well.
We were totally going to get to static
because that's another interview question.
Oh, God, yes.
That's like my first interview question and then volatile. You're just mean. No, I'm not mean.
So, okay. So I've been asked this question. I have asked it. What do you look for? What do you,
what do you want to hear? I want to hear, I want to hear them explain to me how they use it in three different
ways. But I also want them to know that it's, it's limiting. Its purpose is to limit the scope of that
variable or function to the, to that particular area. And I look for them to explain how that's done in three different ways.
Okay so let's see if I put static in front of a function you know static void my function
that means that that function my function is only available in this file no other file has access to
it. And that's specific to C? Oh that that is, yeah, yes. Most of these are specific
to C. Some of them are even specific to compilers, but that one, that allows for encapsulation. It
means that nobody else can play with this function. And it means that only I can, only this file has
access. It's, it's, it's, it's embedded firmware's way of pretending like we're a full-featured C++
language, but we're only C. That's how I view it. That's how I view it.
Going to C++, it's very much like the private keyword. It's a private function if you do it
the way that I just said. You put it in front of a function name. But it's got the same aspect for a file.
It's like putting a private in front of a class variable.
So if you have a global variable in a file that's my age, into my age, if you put a static in front of that, anything inside that file can access that variable.
But other files can't.
Absolutely.
Okay, so that's two.
So those are both like encapsulation methods.
They're ways of making it private.
Making a module look classy and interfaceable,
or not interfaceable, as the case may be.
Yeah, it's hiding, hiding stuff.
Now the last thing is a variable,
a static variable inside of a function.
That one I wish they'd renamed
because that's so broken.
Yeah, because if I set it initially to zero
and then we go back, so okay,
so what happens is every time you go into that function,
whatever you last left my variable,
it will still be, you know, if you
last left it at three, it will, you return there, it'll be three.
Let's frame it first.
So let's say you have a function.
Let's say it's my function again.
And whether it's static or not, the function, if you have your list of variables, you're
declaring and you have int i.
And you know, that's probably going to go into register, register like we said especially if it's incrementing through an index and then you have
this other variable static how many times has this function been called and you set that to zero
so static int how many times this function been called equals zero and right above it you have
int i equals zero. And those
two variables are going to do something very, very different for you. Because when you call
the function the first time, they're both going to be zero. And then if you increment
your i to be a bazillion, the next time you call the function, it's going to come in and
it's going to be zero. Because int going to be zero because int i equals zero.
That's what that means. But static, static, if you come into that function and you increment it once,
then the next time you come into that function, it's going to be one, even though it says static
int equals count number of variables equals zero, it equals one one and the next time you come in if you keep
incrementing it's going to equal two and three and four and it's it's a great way to count how
many times you you've called the functions so so given that so for those who understand yes
it it can certainly help you you know count the number of times you've been in there it can help
you if you have some sort of weird state flag like if you had like well the number of times you've been in there. It can help you if you have some sort of weird state flag.
Well, a lot of times I've used it for, is this the first time I've called this function?
Yes.
Does this need to be initialized?
Run this first time, yeah.
Our producer says that instead of that, for that function of static, the function variable version, they should have called it sticky.
Because that's what it is.
It makes the variable sticky.
Yeah. they should have called it sticky because that's what it is it's it it makes the variable sticky yeah um but you know one of the things that i that always makes me uncomfortable is the fact
that you know we you know when you have static my variable equal to zero yeah inside of inside
of function and you have it equal to zero i always have that uncomfortable reading you know when i
read it i always feel uncomfortable i'm like it's. I'm like, it's always going to be zero. It's always going to be zero. But it's not. I know. I know. But,
but for the reader, to me, it's so uncomfortable. That is a hidden global variable. I don't like
them. I admit I do use them occasionally for the, is this the first time, but overall,
I would rather just slap that variable
to the top of the function,
call it a global inside this file,
and put static up there.
It has the same effect for RAM.
It has the same effect for memory.
Your compiler does essentially the same thing to it.
What it means is that all of your other functions
can modify it, but if you're a good, you know, you put some comments in that says only this function can modify it. Hands it means is that all of your other functions can modify it, but if you're a good,
you know, you put some comments in that says only this function can modify it.
Hands off, baby.
Yeah. Hands off, baby. That needs to be a keyword. Then it's safer because it's not
a hidden global variable. It's a very upfront and in your face global.
Yeah. And i would generally
agree with you except for the fact that people cannot be trusted well there's that i i have met
those people who use my global variables to store their stupid data didn't they know it was my global
variable l's global variable i would make that an array so you can have multiple pieces of data.
Oh, it's a whole structure, baby.
How often do you use the keyword static in your job, assuming you're spending the day programming?
I use it all the time.
I don't think I have used, I don't think I have written a whole code file without it in forever.
Right. Volatile and static are where it's at.
I don't always use volatile in every file
because sometimes if I'm writing like algorithm files,
then those don't need the protections.
But static, static every file
because I like having little helper functions
and those helper functions nobody else should need.
Right.
If you, I mean, when we were in college and we were just trying to get our machine problems done,
I'm sure we could care less about using static.
But now that we're grownups and now we have to make sure that all our modules interface nicely with everyone else's
and we don't want to have to, you know on the on the user that they have to read the
entire file and know what everything does it's really important for us to to make our header
files as accessible and clean as possible without you know making them have to find hidden gems in
the actual dot c file that's true if a function isn't a static function, that's kind of my API.
And all of my files, even the ones that are deep, deep inside my system, have a layer of API-ness that are to be exposed.
And I don't like it when I come to these systems where global variables are shared with lots of different files and functions are called from here to there and the organization maybe they all start with EEPROM or UART but things go everywhere and everybody
calls everything it it makes it creeps me out unfortunately within you know I I say this a lot
to to the people around me when they when they when they laugh at at my C code and they laugh at what I say about making things clean
and distinct and having APIs because most firmware engineers, one, we're about 10 to 15 years behind
what everyone else is doing. And we're usually the only people on our project where there's only one
firmware engineer. So we're not code reviewing each we have very rarely do we see anyone else's code
unless we've been brought in to to save the project so we're just you know we're we don't
think about clean design we don't we don't think about things from a from us a computer science
point of view well there was that triangle and it had ram and and code space and cpu cycles
and nowhere on there did it have able to be maintained right but that that was assumed to
me that that that's like a higher priority than optimization is that in a year someone someone
even other than me should be able to go back and fix it or change it or use it.
Or repurpose it.
Exactly.
But, you know, unfortunately that assumes that your hardware engineer hasn't moved on to bigger and better chips or bigger and better hardware peripherals that require you to basically chuck an entire module.
You really want to, you know, it's nice when we can actually take a module
that we've used in the past
and repurpose it for more stuff.
But to be honest with you,
the number of times that I've been able
to write a beautiful module
and then use it in another project
and it not being consulting related
has been very few times.
Really?
I have a circular buffer library
that I just kind of carry with me all the time
because it's pretty bug-free at this point.
I just mentally end up writing a new one each time,
but it's always effectively the same.
Well, now that I've realized you have to draw a picture
in order to make good circular buffer functions,
then it's gotten a lot better.
But I like that one,. I wrote it on my own
time. And I have a debug library that you can turn on and off debugging levels that I tend to drag
around. But I'm just finishing a project where we're porting probably 60% of the code. And I started to rewrite parts of it
until I realized just how much code they already had.
And then I went searching for what they had,
but it wasn't well documented.
And it was, oh, it's so spaghetti.
And there's the decision of do I port the spaghetti
or do I rewrite it into something more usable?
The new guy who's taking over the project
is very C++-y and is even more wigged
out about the spaghettiness than I am. Because I mean, it's legible. They tried in some areas to
keep the spaghetti down, but other parts, it looks like the spaghetti came up in the worst possible
way. It's a balance. I like to reuse code and I find I do a fair amount of it, but it was easier
when I was a pure software engineer. Well, you know, we're talking about spaghetti code here.
Maybe one thing that we could do to give to the audience to help limit the spaghetti code is
talking about macros. You know, in the spaghetti code, one of the macros is code generation. It reads a
text file and then generates a bunch of C code by having different definitions for the macro that is
defined in the text file. It's hideous. Never do that. Whoever you are, if you did that, please
stop. Use Python, use C to generate your code and then compile it. Don't use the preprocessor to generate your code.
Okay, I'm sorry.
That was not what you wanted.
You didn't really mean rant on that one.
I'm sorry.
So let's take it back to macros.
Oh, yeah, macros.
Macros are wonderful.
Pound to find is my friend.
What can a macro do for us?
Well, let's see. The reason that the macro min, you know, when you have A, B,
is often implemented is because there is a very short way to do that. But it's ugly. That trinary
operator with the question mark in the colon, it's helpful to the compiler, but I hate seeing it. It
just isn't the way my brain works.
And I always wonder if the first one is true or the second one is true. So, you know, I do that.
I do the min and then it's type independent. That's one of the great things about macros.
If you want a macro to tell you to square something, you could square a car. It doesn't
matter. Or or char however you
want to say that it makes you make it sound like it's a little bit like a like template classes in
a way or or operator overloading in a way oh yeah i mean you can't you can't macro uh the star the
asterisk to anything that would be kind of fun. Code obfuscation at its best.
Oh my God.
You can macro pound define if to be else.
Yeah, it would be horrible and mean.
I'm sorry I was making a face.
Or just pound define if to nothing
and then all of your if statements run
whether you meant them to or not.
Don't do that.
I'm sorry.
I'm hurting Jack. You'm really big of my face so let's talk about the basics basics are about when you'd actually want okay so so
macros versus type def uh type def so if you if you have a macro for it it's effectively replacing a snippet of code
however if you pound if you if you use type def to say like hey cast all in all ints to
to cast uh whenever we say uh that hurts baby and replace that with an int,
that's just replacing one word for another.
That happens in the preprocessor stage.
No checking happens.
It's as though you used an awk or Python or Perl
or whatever your favorite scripting language script
to just go through and replace everything.
And define is effectively doing the same thing there too.
No, no.
Define is replacing everything.
Wait, typedef is?
Typedef is more of an alias.
Yes.
And macro, a pound define is a true redefinition.
Yes.
And some of the other things that you should be aware of on macros are put parentheses around
stuff in your macro because you never know what people are going to pass into your macro
well for my min function min a b it would take those a and b as parameters and return whichever
one was the lesser you can pass you know i plus plus I++. And now the question is, well, I++, you should never pass to that
because it's accessed multiple times.
Yes, that too.
But you could pass I++.
And if you don't have the proper parentheses around each variable,
it could go really bad because now you have the plus three kind of hanging out there.
So if you have min A, B, you know, every time you use A, you should have parentheses around it.
Every time you use B, you should have parentheses around it.
And when you finish with that whole macro definition, you're going to have a lot of parentheses.
And that's okay.
Parentheses are cheap.
If you're having trouble finding the parentheses key, just let me know.
I'll buy you a pack of 10.
I want curly braces.
Yes.
Curly braces are also cheap.
Yes, yes.
Single line if statements still need curly braces.
In my book.
Okay.
So one of the bad things about pound define,
I mean, with the whole redefinition, there's no checking. But also we talked earlier about const and pound define i mean with the whole redefinition there's no there's no checking
but also we talked earlier about const and pound define every time you define something and you put
it in your code it's actually replacing it's putting that code in your code yeah it is really
the script replace and so it can take space it can take processing power if you are putting in something that is
secretly using a lot of cycles it can generate confusing errors that will be difficult to track
down yes because they're inside your macro and when you're using your debugger it's a it's like
a single step and all you say is like max and you're like yeah well max is just blah
yeah it's a function you can't debug it's a function you can't yeah because you have to go
up and see and then you have to mentally go like well i'm just putting in these two variables just
like we talked about before if i do i plus plus why is i suddenly this or why how did i get to be
12 even though all i did was a min variable and it started out being one right yeah although i question how you wrote max if it's suddenly 12 and it was you would put
it at one incredibly inefficiently that's the other thing is that you know you're using macros
and maybe you didn't read you didn't read your macro or read whoever wrote that macro. So I would say use them carefully because they can have unintended consequences.
But they can also be very fast.
Yeah.
For example, the min function, if you used a real function, then you have to push your registers onto a stack.
You go to the new function.
You do the function stuff and you pop the registers off the stack, which is a lot of function, you do the function stuff, and you pop the
registers off the stack, which is a lot of work just to find the minimum of two variables. And
if you have to do that for floats and doubles and ints and uints, you end up with 25 functions when
really you could do a macro and it's simple. Right. You let the compiler do what it does best is figure
figure out what the hell you were trying to talk about to begin with let's see um actually you know
we also didn't talk about inline well inline i mean i don't see it used all that often anymore
it's still a way of telling the compiler this function is super short. Go ahead and replicate it everywhere you want to. It's a lot like a macro except it still has
typejacking. Yeah. And some compilers I've used you can debug an inline and some you can't.
Depends on how it puts that together. It's kind of like optimizing. It's confusing. It is a little
yeah. But a lot
of times with the inlines, you don't have the push and pops either. It really is just copying
that one line of code to wherever that function is called. Right. But hopefully it's going to be
more than one line of code for your inline. Well, you know, those four lines of code. Right.
We use really big lines. You jack the font size up to like 25.
But inline is another way of really getting to know your compiler and knowing what it's going to do and checking that list file.
And we talked a little bit about pragma pack, but we need to talk about other pragmas.
No.
One of the pragmas that i see
a lot for embedded systems has to do with memory spaces especially for people uh who need to put
certain pieces of code in certain places if you're writing a bootloader the bootloader has to go in
one place and the application code needs to go in another and so you have to tell the code what's
what and if there are variables that are passed from one to another,
one area of code to another,
they may need to be hard-coded at a certain location.
They may need a near-far pointer to be generated.
Well, sometimes there's the at symbol that says
put this variable at this location in memory,
and sometimes there's a pragma that will do the same thing.
That is pretty compiler dependent.
And it's also useful even for functions.
I mean, the bootloader code may need to be in a separate area,
but also some functions may need to run in RAM
if RAM has fewer wait states,
if it's faster than other code space.
So certainly knowing not only how your pointers are working, but also how your memory works
if you are writing functions or if you're doing embedded systems where you're moving
things around in interesting ways.
The one that I see most often for pragma is pragma interrupt.
But that wholly depends on what compiler you're using and what chip you're using.
But don't be surprised if you ever have to use pragma just to do an interrupt.
And sometimes there's an interrupt keyword, but that's very compiler dependent.
Yeah.
Which is why we highly recommend reading your compiler's user's guide.
There is something special about knowing a tool in your toolbox.
Last time Jen was a guest, we talked about voltmeters,
and listening back to that show,
you can hear the affection we have for our voltmeters,
for our particular ones.
We each had them.
And programming languages are a very sophisticated tool.
I like knowing the oddities.
I like occasionally reading some of the generated assembly.
The list files are magical.
And I do it sometimes to see if I'm smarter than the optimizer.
But that means you have to understand the language.
So if you're one of those people who
only read the first half of the book for whatever language you have, seriously, go back. Read the
good stuff. It's towards the end. Read the appendices. The neat, the really neat stuff
starts long after the conditional and loops are described. So what's one of your favorite keywords
that you found in the appendix?
Oh, volatile.
And not only because it is so very, very useful,
but because it's a good word.
I mean, situations are volatile.
Chemicals are volatile.
Fourth of July is a volatile holiday.
I just, it's a great word.
Pea stocks are volatile.
Yes.
What about you?
What's your favorite hidden gem?
That one's a difficult one.
If I was going to talk about something that was, first I'm going to give you like kind
of like a basic one in, in, in, within C and C++.
And those are going to be the error and warning.
Oh, pound error and pound warning.
Yeah, because those can prevent you from doing a lot of stupid stuff.
Ah, yes.
Yes, I have one project where it's a pound warning.
You have compiled with JTAG enabled.
If this is a production build, shoot yourself in the foot again,
and I will not save you.
Well, you could type that.
But you could type something like, for example,
you're just trying to figure out which files are included in your build
and which ones aren't.
You can always just put a pound error,
and you'll find out really quickly if that file is being included.
I know that's kind of a cheating way of doing that,
but if you have a very complex, let's say you're in Android,
and you're just trying to figure out which file it's pulling from based on whatever lunch option
you have. I know there's lots of Android people out there that are listening to this, I'm sure.
And just being able to put in a pound error can save you hours of trying to figure out what the
hell all these different make files are trying
to include into your project.
And because the pound warnings happen in the order the preprocessor gets them,
you can use it to figure out what order your header files are happening.
And pound error is great for if you are looking for a define and if that define doesn't exist,
say hardware version.
Or it's different than when you expected it to be.
You can say, look, I can't compile because I don't know what it was you meant.
I don't know what hardware version you're running, so I'm not going to generate anything for you.
Right. So if you thought you were going to be compiling Bob's electric pants, but then later on it turned into Trudy's electric skirt, you can use that pound warning to basically alert you to that fact.
Yeah. Pound warning. You don't have pants on.
You don't?
Okay. So that's my favorite C ones. But if we're going to talk about peculiar
ones that I've come across, it has to be in C sharp. And, you know, so, so many times you,
you know, you see, C and C++ people out there, you're writing code, you're using pointers all
willy nilly. And now you have to write an app in Java or C sharp sharp that's going to interface with your c tool that you've made to interact
with the rest of your product and so using c sharp and you've used a pointer and you get this
really peculiar warning that says whatever you're doing you're using a pointer and it's unsafe
oh unsafe pointers right because pointers are unsafe, but that's okay because we're grown-ups and we can handle it.
Right.
In the C and C++ world, we are grown-ups.
We've been doing this for ages, but because, well, we're humans and we make lots of mistakes,
computers have deemed that unsafe.
And so they want to make sure that they're told when unsafe code is being written.
So there's actually a keyword in C Sharp that's called unsafe.
And so that's like the contract that you sign when you're about to go bungee jumping that says,
I recognize that if I die, it's my own fault.
So the unsafe keyword is my contract to the compiler that says,
I recognize that I am destroying the safety of your system.
Yeah, basically, that I could create hilarious memory leaks.
I could malloc and alloc and, well, totally forget.
Forget to type free.
So, yeah, so don't be surprised if you're in C, C++ world
and you're writing lots of pointer-intensive stuff
and then you need to do something in C Sharp to interface it
and you get these weird errors that talk about how unsafe you
are and you don't know what to do, just type the keyword unsafe ahead of it and you'll be okay.
We could add that to C, but we'd have to keep typing it over and over and over again.
Well, thank you for chatting with me. I do hope you'll be back soon.
I hope I will be back soon in one piece.
Yes, that startup is going to be exciting.
Yay.
Thank you to Christopher White for producing the show.
It isn't an easy job, but he's really good at it.
Thank you to listener Nick for your kind words and offer of production help.
I really appreciate it.
And if you'd like to help or be on the show
or tell us your favorite part of a language
or ask questions or just say hello,
hit that contact link at embedded.fm
or email us at show at makingembeddedsystems.com.