【C字符串处理】【参数传递】 一次又傻又颇有成就感的 服务器自动编译 参数传递 调试经历

故事背景

在使用Telink TLSR8258芯片做一款Zigbee产品的时候,需要将代码放到公司的编译服务器上自动编译,实现SCM自动化编译流程。

需求

做事情之前,搞清楚需求。我的需求就是配合SCM部门人员,实现代码在服务器上的自动化编译。他们搭建环境,然后告诉我,我需要从服务器读取的参数,他们可以适当修改我的代码来实现参数从服务器传递到代码中,就好像命令行传递参数,或者makefile定义C代码中的变量。

一切都基本搞定后,最后留下一个需要实现的需求,就是将服务器传递的版本号"v1.00.01" 传递到代码中进行编译。我起初以为传递进来的是个字符串,而实际上并不是,这个下面会提到。

代码中原始的对版本号的定义方式是这样的:

#define ZCL_BASIC_SW_BUILD_ID  	{8,'v','1','.','0','0','.','0','9'}

被定义成这个亚子,是有它的原因的,涉及到代码细节不展开讲,简单说,就是为了组Zigbee的包方便。所以,想要修改这个传递的内容形式,也应该在这个宏传递进来的地方修改,而不是到代码底层去修改组包的方式。

实现 - 误以为是字符串

Step 1. “数值+字符”–>“数值+字符串”

尝试将”数值+字符“型的数组变为”数值+字符串“型的数组。这样,只要能从makefile传递进来一个字符串,我就可以直接组合为新的包。
首先想到的方法是,直接修改宏定义为”数值+字符串“:

#define ZCL_BASIC_SW_BUILD_ID  	{8,"v1.00.01"}

结果,warning: initialization makes integer from pointer without a cast (用指针初始化整形,没有使用强制类型转换(cast))
那么,强转试试:

#define ZCL_BASIC_SW_BUILD_ID  	{8,(u8*)"v1.00.01"}

结果,warning: initialization makes integer from pointer without a cast ,一个道理,我传递的仍然是一个指针。

Step 2. “字符串指针报错”–>暴力制造“整形数组”

于是乎,暴力制造一个来自于字符串的整形数组:

char t[] = "v1.00.01";
#define ZCL_BASIC_SW_BUILD_ID  	{8,t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7]}

报错:error: initializer element is not constant(初始化元素不是常量),在这个位置:

zcl_basicAttr_t g_zcl_basicAttrs =
{
    ...
	.swBuildId		= ZCL_BASIC_SW_BUILD_ID,    //Error here
};

对啊,怎么能用变量来初始化结构体呢,加个const,虽然仍是变量,但声明为常量了,也许行?(这都是C语言基础差的人能想出来的损招)

const char t[] = "v1.00.01";
#define ZCL_BASIC_SW_BUILD_ID  	{8,t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7]}

呵呵,这不就是自欺欺人么:error: initializer element is not constant

Step 3. “全局初始化”–> “使用前重赋值”(重要转折)

但是等等,我突然想到,我为什么偏偏要改人家初始化的值呢。我可以让它全局初始化之后,在代码中,在它被使用前,给它赋值啊。于是乎,我找了代码中比较靠前的一个地方,加入了如下语句:

void user_init(void)
{
    const char t[] = "v1.00.01";
	g_zcl_basicAttrs.swBuildId = {8,t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7]};
     ...
 }

滑稽了,数组只有初始化的时候才能用花括号,哪个老师,哦不,哪本书教你可以用花括号给已经初始化的数组赋值了。Sorry,是我C语言不过关。

Step 4. "strcpy/strncpy"赋值(重要实现方法)

那,干脆,我拼一个数组好了,用strcpy可以拼接数组,还可以指定从数组的哪个位置开始拼:

#include "string.h"
		const char t[] = "v1.00.01";
		char dest[20] = {0};
		strcpy(dest, t);
		printf("---Dest(%d) %s\r\n", strlen(dest), dest);

我并不确定在这个系统中可以使用string.h和对应的函数,有些嵌入式系统连基本的C函数库都不全的。但是很庆幸,没有报错,看打印:

-Dest(8) v1.00.01

熟悉C库的你,可能已经想到了,strcpy并不能等效为”字符拼贴“,查一下strcpy的定义,你会发现,拼贴后的字符串,是”字符串“,也就是有结尾的NULL字符。我们原始的表达方法是纯正的”字符拼贴“不带NULL字符,这就让我想到要截断,用strncpy规定要取的字符数,而不取最后的NULL字符,对照一下strncpy确认这个函数不会自己添加NULL字符。然后,我还发现并不需要#include"string.h",可能上面有别的已经include过了。先Hard code 验证下:

const char t[] = "v1.00.01";
char dest[20] = {0};
strncpy(dest, t, 8);
printf("----Dest(%d) %s\r\n=\r\n", strlen(dest), dest);

虽然得到相同的打印,但自己心里清楚,这回没有结尾的NULL了。接下来就是将这个代码的Hard code转换为正常代码,改一点测试一下:

#define TEST_VERSION_STR	"v1.00.01"
		g_zcl_basicAttrs.swBuildId[0] = strlen(TEST_VERSION_STR);
		strncpy(g_zcl_basicAttrs.swBuildId+1, TEST_VERSION_STR, strlen(TEST_VERSION_STR));
		printf("----swId(%d): %s\r\n\r\n", strlen(g_zcl_basicAttrs.swBuildId), g_zcl_basicAttrs.swBuildId);

报错:warning: pointer targets in passing argument 1 of 'strncpy' differ in signedness,note: expected 'char *' but argument is of type 'u8 *',说白了就是strncpy这个函数的参数应该用char*,而不是u8*,还是强转的问题。改一下:

#define TEST_VERSION_STR	"v1.00.01"
		g_zcl_basicAttrs.swBuildId[0] = strlen(TEST_VERSION_STR);
		strncpy((char*)g_zcl_basicAttrs.swBuildId+1, TEST_VERSION_STR, strlen(TEST_VERSION_STR));
		printf("----swId(%d): %s\r\n\r\n", strlen((char*)g_zcl_basicAttrs.swBuildId), g_zcl_basicAttrs.swBuildId);

打印为:

--swId(9):v1.00.01

step 5. 整理,关联服务器参数

下面是代码的一个整理,将 TEST_VERSION_STR 这个宏,和服务器传入的 ZCL_BASIC_SW_BUILD_ID 这个宏进行关联:

#ifndef ZCL_BASIC_SW_BUILD_ID     //如果服务器没有定义,则使用默认定义
#define ZCL_BASIC_SW_BUILD_ID  	"v1.00.01"
#endif
#define ZCL_BASIC_SW_BUILD_ID_INIT_VALUE  	{8,'v','1','.','0','0','.','0','9'}

zcl_basicAttr_t g_zcl_basicAttrs =
{
    ...
	.swBuildId		= ZCL_BASIC_SW_BUILD_ID_INIT_VALUE,
};
void user_init(void) {
    ...
     g_zcl_basicAttrs.swBuildId[0] = strlen(ZCL_BASIC_SW_BUILD_ID);
     strncpy((char*)g_zcl_basicAttrs.swBuildId+1, ZCL_BASIC_SW_BUILD_ID, strlen(ZCL_BASIC_SW_BUILD_ID));
     //printf("----swId(%d): %s\r\n\r\n", strlen((char*)g_zcl_basicAttrs.swBuildId), g_zcl_basicAttrs.swBuildId);
     ...
}

好了,万事大吉,上传代码!
Buuuuuuuut!本地编译没有任何问题的代码,到服务器上编译出错了。服务器是这么说的:

21:44:00 ../apps/TRTL_Sensor/app_device.c: In function 'user_init':
21:44:00 ../apps/TRTL_Sensor/app_device.c:116:47: error: expected expression before ';' token
21:44:00 ../apps/TRTL_Sensor/app_device.c:121:74: error: expected expression before ',' token
21:44:00 ../apps/TRTL_Sensor/app_endpoint_cfg.c:62:43: error: expected expression before ',' token

step 6. 传递空 != 传递0

找到对应的116:47等的位置,发现恰好都指向 ZCL_BASIC_SW_BUILD_ID,后面的符号,根据报错,怀疑这里的宏被设置为空了。让服务器管理人员查看一下,果然是这样,在当前的build 环境下定义为空,release的时候会赋值,但是当前build不过,是不能release的!放到代码里,相当于执行了

#define ZCL_BASIC_SW_BUILD_ID

也就是定义了,但定义为空,我又一次C语言不过关地认为,我只要把代码这么修改,就可以解决:

#ifdef !(ZCL_BASIC_SW_BUILD_ID)
#define ZCL_BASIC_SW_BUILD_ID  	"v1.00.01"
#endif

但不是这样的!空不代表“0”也不代表“NULL”,空就是空,服务器大佬这么讲:#if 后面要有表达式,空不是表达式。(科班出身就是不一样)然后服务器大佬修改了一下服务器,给出了默认值。

但仍然编译失败了!?服务器是这么说的:

12:59:36 ../apps/TRTL_Sensor/app_endpoint_cfg.c:69:16: error: too many decimal points in number
12:59:36 ../apps/TRTL_Sensor/app_endpoint_cfg.c:69:16: error: 'v0' undeclared here (not in a function)
12:59:36 ../apps/TRTL_Sensor/app_endpoint_cfg.c:69:16: error: expected '}' before numeric constant
12:59:36 make[2]: *** [apps/TRTL_Sensor/app_endpoint_cfg.o] Error 1

多蹊跷啊,这些报错:"在数字中有太多的 十进制小数点”,“‘v0'不能识别”,“数值常量前没有花括号”。服务器大佬见状,立刻反应过来了原因,服务器传递的是makefile中定义为 v0.00.00 的变量,而不是我想要的字符串。相当于什么呢:

#define VALUE_FROM_MK	v0.00.00

而我希望的是:

#define VALUE_FROM_MK	"v0.00.00"

实现 - 将没有类型的宏变为字符串

Step 1. 讨论,定方案

电联服务器大佬,给出了三种解决思路:

  • 通过函数将宏中的内容一个个编入字符串;
  • 在makefile中做文章;
  • 下下策,修改服务器传递参数的方法

Step 2. C语言#的妙用

Buuuuuuuuttt,我竟然全都没有用。
首先,我又“C无知”的信手想要一步到位地定义了如下宏:

#ifndef ZCL_BASIC_SW_BUILD_ID
#define ZCL_BASIC_SW_BUILD_ID  	v1.00.01
#endif
#define TEST_VERSION	"ZCL_BASIC_SW_BUILD_ID"

一看不对啊,字符串不成了“ZCL_BASIC_SW_BUILD_ID”了,愚蠢地又尝试了"\"" 的转义字符方法,和"""" 的转义字符方法,自然都是不对的。这个“添加双引号”的小功能,我就不信只有我被困住过,Baidu吧!“C 宏定义 字符串”。果然,找到了“#”相关的使用,似乎能解决这个问题,再次感谢CSDN。
于是改写code:

#ifndef ZCL_BASIC_SW_BUILD_ID
#define ZCL_BASIC_SW_BUILD_ID  	v1.00.01
#endif
#define STR1(R)  #R
#define STR2(R)  STR1(R)
#define ZCL_BASIC_SW_VERSION	STR2(ZCL_BASIC_SW_BUILD_ID)

void user_init(void) {
    g_zcl_basicAttrs.swBuildId[0] = strlen(ZCL_BASIC_SW_VERSION);
    strncpy((char*)g_zcl_basicAttrs.swBuildId+1, ZCL_BASIC_SW_VERSION, strlen(ZCL_BASIC_SW_VERSION));
}

编译,烧录,验证OK,终于顺利Release!

Step 3. 知其然,更要知其所以然

C语言“#”这一使用方法,可谓“妙用”,搞懂它! 找一篇网文学一下就好。

总结

这一切的不顺利,全都怪C语言基础太差,走了很多弯路,换做C大神,可能一想就知道走错了,哪还会“试来试去”的。

但这一切又顺利解决了,蛮有成就感,可能源于坐的住,相信问题总能解决,只要磨洋工,啊不,只要肯钻研。想到当时笔试E某大厂,猎头说我是他这个月介绍的所有人中唯一一个笔试通过的,我都没想过自己能过,2小时2道题的笔试,后面的题是汽车拍牌系统的建模,读题都很难读懂,我愣是一动不动一点一点把整个模型实现了。我觉得真正让那些选手被淘汰的是不自信,不认为自己有能力解决这个读都读不懂的题,于是觉得系统很复杂。我一个以上C语言基础,写代码靠试靠蒙的人,凭什么能比得上那些准备良多的C大神?可能就是坐的住吧。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值