Contents

前段时间,我在为步进电机撰写驱动程序的时候,遇到了一个问题。当时是做一个比赛,所以时间比较匆忙,我就没有仔细考虑,为了求快,导致写代码比较随意。然后就写出了一个bug,我当时是没有办法解决因为我很急,而且事情很多。现在事情过去了,我就写一个总结。

当时我在motor.h头文件里写了大致如下的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
typedef struct {
// ...
} step_motor_t;
static step_motor_t g_motor1 = {
.xxx = xxx,
// ...
};
static step_motor_t g_motor2 = {
.xxx = xxx,
// ...
};
// ...

然后我就发现一个问题,编译器告诉我,g_motor1重定义,我当时觉得是因为我没有加header guard,然后我就加了。

之后就发生了一个神奇的事情,编译器不再抱怨我的代码有错误,但是我就是不能在中断里面通过全局变量来修改状态。之后,我尝试各种方法都不行。然后我利用ST-LINK进行单步调试,发现同样叫g_motor1的变量,即使是在函数里没有改变这个变量,只是读取,但是在进出函数时候有两个状态。

这个时候,我想到可能有两个都叫做g_motor1的变量,于是就写了一个demo来测试和印证我的想法。下面是具体的代码。

motor.h的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef MOTOR_H
#define MOTOR_H

#include <stdint.h>

typedef struct {
int32_t speed;
// ...
} step_motor_t;

static step_motor_t g_motor1 = {
.speed = 300,
// ...
};

static step_motor_t g_motor2 = {
.speed = 300,
// ...
};

void motor_init();

#endif // !MOTOR_H

motor.c的内容

1
2
3
4
5
6
7
8
9
#include "motor.h"
#include <stdio.h>

void motor_init()
{
printf("Motor1 speed: %d\n", g_motor1.speed);
printf("Motor2 speed: %d\n", g_motor2.speed);
// ...
}

main.c的内容

1
2
3
4
5
6
7
8
9
10
11
12
#include "motor.h"

int main()
{
g_motor1.speed = 400;
g_motor2.speed = 500;
motor_init();
printf("Motor1 speed: %d\n", g_motor1.speed);
printf("Motor2 speed: %d\n", g_motor2.speed);
return 0;
}

事实却是是如此,输出内容如下。

1
2
3
4
Motor1 speed: 300
Motor2 speed: 300
Motor1 speed: 400
Motor2 speed: 500

完全和预期不一样,我想应该大多数人和我一样,都觉得g_motor1.speed = 400;会改变motor_init函数内的输出内容。实际上并不是这样的,通过观察汇编,发现对于g_motor1一个地址是02FA000h,另外一个是02FA00Ch

这就说明,一个名字可能是有两个变量。造成这样的原因就是因为static的误用。

在我的固有印象里,如果在头文件里写下面这样的代码,是会重定义的。

1
2
3
4
void motor_set_speed(step_motor_t* motor, int32_t speed)
{
motor->speed = speed;
}

为此,我们通常会为函数加上static修饰,从而能够避免重定义,因为static可以把范围框定在文件内。通过VS2022来观察,会发现不管是在motor.c还是main.c内调用的motor_set_speed的函数地址是一样的。于是,就很容易推论到变量上也是一样的来解决重定义问题,但是实际上会导致变量变为多份。

这根本原因是在于c文件#include的时候,头文件中的static变量每次被拷贝到一个c文件里,就会产生新的一份,而且他们的名字都一样。

因此就总结出来一条经验,禁止在头文件内定义static变量,而是按照下面这样的方法,在头文件中extern,然后在源文件中定义static变量。

motor.h的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef MOTOR_H
#define MOTOR_H

#include <stdint.h>

typedef struct {
int32_t speed;
// ...
} step_motor_t;

extern step_motor g_motor1;
extern step_motor g_motor2;

void motor_init();

#endif // !MOTOR_H

motor.c的内容。

1
2
3
4
5
6
7
8
9
10
11
12
#include "motor.h"

static step_motor_t g_motor1 = {
.speed = 300,
// ...
};
static step_motor_t g_motor2 = {
.speed = 300,
// ...
};

// ...

一般来说,我都尽量不使用全局变量,但是在嵌入式开发中,因为中断函数没有办法传参,所以只能通过全局变量来解决。

Contents