手机号码patch

昨天更换手机号码了. 原号码不再使用.

如何获取我的新号码

  • 2. 如果没有我原来的号码: 在页面手机号码更换获取我以前的号码, 然后跳转到1.

 

ARM平台下位域结构体的问题

最近在做个IP camera, 开发板是Hi3511的. 在写h.264码流封装成rtp包进行网络传输时, 出问题了: 在客户端用vlc看不到期待的画面. 用wireshark抓包, 把源源不断的rtp包拆包一看, 竟然没有fu-a包, 全是单个的nal包. 而输入的nalu基本都是大于最大传输单元MTU的, 需要分解成fu-a包的形式. 通过测试发现, 问题出现在设置fu分片包的indicator和header上. 再往下分析, 得到在ARM平台下令人惊奇的现象. 假如我们有下面代码:

#include <stdint.h>

typedef struct fu_indicator {
    uint8_t type: 5;
    uint8_t nri: 2;
    uint8_t f: 1;
} fu_indicator_t;

int main(int argc, char **argv)
{
    uint8_t n = 7;
    fu_indicator_t *pstf;

    pstf = (fu_indicator_t *) &n;
    pstf->type = 0x0f;
    printf("now n is %d\n", n);
    return 0;
}

以上代码在x86平台上运行结果正如预想的一样, 但在arm平台下就不一样了, 结果竟然是n变成0了. 看汇编内容:

main:
@ args = 0, pretend = 0, frame = 12
@ frame_needed = 1, uses_anonymous_args = 0
mov ip, sp
stmfd sp!, {fp, ip, lr, pc}
sub fp, ip, #4
sub sp, sp, #12
mov r3, #7
strb r3, [fp, #-13]     @ 在内存单元[fp, #-13]地址上保存n=7
sub r3, fp, #13         @ r3放n的地址
str r3, [fp, #-20]      @ 将n的地址保存在内存单元[fp, #-20]上, 即pstf的地址
ldr r2, [fp, #-20]      @ 将n的地址加载到r2上
ldr r1, [r2, #0]        @ 将[r2, #0]的内容即n = 7加载到r1上
str r1, [fp, #-24]      @ 将r1即n = 7保存在[fp, #-24]上
ldr r1, [fp, #-24]      @ 加载地址[fp, #-24]的内容即n = 7到r1
bic r3, r1, #16         @ 将n & ~0x10 = 7 放在r3
orr r3, r3, #15         @ 将7 | 15 = 15 放在r3
str r3, [fp, #-24]      @ 将结果r3 = 15 保存在内存单元[fp, #-24]
ldr r3, [fp, #-24]      @ 加载[fp, #-24] = 15 到r3
str r3, [r2, #0]        @ 保存r3 = 15 到 n的地址[r2, #0]上, 之后n应该=15
ldrb r3, [fp, #-13]     @ zero_extendqisi2
mov r0, r3
sub sp, fp, #12
ldmfd sp, {fp, sp, pc}
.size main, .-main
.ident "GCC: (GNU) 3.4.3 (release) (CodeSourcery ARM Q3cvs 2004)"

汇编代码文件看起来没有问题, 但问题是程序跑出来的结果n是不等于15的. 这说明出现问题的阶段是发生在编译阶段之后的, 也许是汇编阶段, 也许是链接阶段. 据老夫多年行医经验来看, 汇编阶段不过是将汇编指令的机器码查找出翻译成机器码就可以了, 像查英汉字典一样有固定解释, 很少有出错的可能, 基本可诊断为问题出现在链接阶段, 而像地址分配, 内存对齐,重定位等都在这一阶段完成.
接下来用sizeof(fu_indicator_t)一看, 在ARM平台上竟然是4, 不是x86平台下的1了. 我试着改这个结构体里面各个位域的不同值发现, 各个位域的位置是随机的呀! 连顺序都会改变呢! 就是说这个结构体的位域的位置连顺序也不保证. 你见过一个结构体的比特序都会变化的吗? 当fu_indicator结构体里面的type位于后三个字节的时候当然就不是我预想的结果了.

解决办法: 强制这个结构体按最紧凑的方式对齐, 可以在声明fu_indicator结构体后加入”__attribute__ ((packed))” 或在Makefile里的加编译选项”-fpack-struct”, 这样fu_indicator结构体的长度就为1字节了, 比特顺序也能得到保证. 或按最保险的方法: 放弃用位域这种结构, 而直接用按位操作来代替.

总结: 位域这种结构移植性是非常差的. C语言参考手册说了: 依赖于存储策略是危险的, 原因有几个. 1, 不同的计算机对数据类型的对齐限制不同. 2, 位字段宽度的限制不同. 3, 字节序不同. 甚至比特序也不同, 像上面的例子在arm平台就在同一个结构体上出现了截然相反的比特序. 如处于移植性考虑, 尽量不要用位域这种结构体来实现设置位的操作, 而是直接用按位操作.

另: 从这个帖子来看, 这个问题还和GCC的版本有关.

递归中的改进

上一篇(一次面试)中, 我用递归实现了输出所有可接受序列的排队方法. 后来, 我意识到, 在递归中, 频繁地申请内存来存储新增加的可接受序列的字符串, 再释放掉这片存储区域的方法是不必要的. 因为在递归中每调用一次新的递归后, 这时的可接受序列的字符串前面部分是一样的, 可以每次调用递归函数都用这块存储区域. 这时我们就不能用strcat()函数来加入新的字符了, 而是要先计算出目前要修改的字符的位置(pstr + (num_of_push_fiftycents * 2 – sum_of_fiftycents_inbox)), 然后直接在这个位置上修改要加入的字符. 改进后的代码如下:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define USEMALLOC   0

static unsigned int COUNT = 0;

static void
sale_ticket(int num_of_push_fiftycents, int sum_of_fiftycents_inbox, char *pstr, const int num_of_fiftycents)
{
	/*
	 * 若钱箱内五角钱币为空, 且还有五毛游客排队时,
	 * 则下次只能接受五毛游客购票
	 */
	if (sum_of_fiftycents_inbox == 0 && num_of_push_fiftycents < num_of_fiftycents) {
#if USEMALLOC
		strcat(pstr, "F");	/* 把"F"加到字符串尾部表示有五角进了售票员的钱箱 */
#else
        *(pstr + (num_of_push_fiftycents * 2 - sum_of_fiftycents_inbox)) = 'F';
#endif
		sale_ticket(num_of_push_fiftycents + 1, 1, pstr, num_of_fiftycents);
	}

	/*
	 * 若钱箱内五毛钱币非空, 且还有五毛游客排队,
	 * 则下次可以接受两种游客的购票请求
	 */
    if (sum_of_fiftycents_inbox > 0 && num_of_push_fiftycents < num_of_fiftycents) {
		/* 情况1: 下一张票卖给1元游客 */
#if USEMALLOC
		strcat(pstr, "T");	/* 把"T"加到字符串尾部表示有1元进了售票员的钱箱 */
#else
        *(pstr + (num_of_push_fiftycents * 2 - sum_of_fiftycents_inbox)) = 'T';
#endif
		sale_ticket(num_of_push_fiftycents, sum_of_fiftycents_inbox - 1, pstr, num_of_fiftycents);

		/* 情况2: 下一张票卖给五毛游客 */
#if USEMALLOC
		strcat(pstr, "F");
#else
        *(pstr + (num_of_push_fiftycents * 2 - sum_of_fiftycents_inbox)) = 'F';
#endif
		sale_ticket(num_of_push_fiftycents + 1, sum_of_fiftycents_inbox + 1, pstr, num_of_fiftycents);
	}

    /*
     * 排队的只剩下手持一元的游客,
     * 下一张票只能卖给这类游客了
     */
    if (sum_of_fiftycents_inbox > 0 && num_of_push_fiftycents == num_of_fiftycents) {
#if USEMALLOC
        strcat(pstr, "T");
#else
        *(pstr + (num_of_push_fiftycents * 2 - sum_of_fiftycents_inbox)) = 'T';
#endif
        sale_ticket(num_of_push_fiftycents, sum_of_fiftycents_inbox - 1, pstr, num_of_fiftycents);
    }

    /*
	 * 当所有游客都买到票了,
	 * 则输出之前记录的买票顺序字符串
	 */
	if (sum_of_fiftycents_inbox == 0 && num_of_push_fiftycents >= num_of_fiftycents) {
		COUNT++;
		printf("%s\n", pstr);
		return;
	}
}

int
main(int argc, char **argv)
{
    char *prt;
    int max_push;   /* 五毛游客数, 即五毛进钱箱的最大次数 */

    if (argc < 2) {
        fprintf(stderr, "usage: %s num\n", argv[0]);
        exit(0);
    }
    max_push = atoi(argv[1]);
#if 0
    if (max_push <= 0) {
        fprintf(stderr, "usage: %s num\n", argv[0]);
        exit(0);
    }
#endif
    if ((prt = malloc(2 * max_push + 1)) == NULL) {
        perror("main: malloc");
        exit(1);
    }
    memset(prt, 0, 2 * max_push + 1);

    sale_ticket(0, 0, prt, max_push);
    printf("The total number is: %d\n", COUNT);
	free(prt);

    return 0;
}

不过, 频繁的申请, 释放内存并不是这个程序的瓶颈. 修改后的程序运行时间基本是和原来一样的. 原因之一是每一次调用malloc申请到的其实是同一片区域, 没有调用到sbrk()申请过一片大的存储区域. 另一个原因是递归实在是效率低下, 没有留给别的东西当瓶颈的机会.

一次面试

 

上周六(12.24)去X公司面试, 打了趟酱油. 收获还是有的.

上午十点来到X公司. 先是笔试: 题目类型是Linux C++的, 120分钟. 题目还记得些.

前面几题是问答题: 1.Linux下malloc函数的实现. 只依稀记得 K&R 里大概提了下. 2. errno的作用及实现. 实现我还真不知道怎么答. 3. sync命令的作用. 简单的说: 同步缓冲区数据到设备. 4. 好象是问TCP连接中如何确定send的数据被接收方全部接受完. 不知道是不是通过接收方对数据包进行对齐检校后再回应发送方而实现的. TCP是可靠连接嘛. 5. Linux中线程和进程的异同. 这个问题每一本讲Linux编程的书应该都有详细解释. 然后是几道编程题: 前面3题都是简单的字符串操作之类的, 没什么好说的. 看到第4题的时候觉得有点意思了:

  某公园门票5角, 有5个游客手里有且仅有一张5角纸币,
  另外5个游客手里有且仅有一张1元纸币. 售票员手中
  没有任何纸币. 设计一个程序演示这10个游客可使售票员
  能顺利找零的所有排队方法.
  例如 "5 5 5 5 5 10 10 10 10 10" 这种方法就能顺利找零.

这题看起来眼熟了(后来回来才回忆起在Richard.A.Brualdi的那本组合数学里看的, 著名的Catalan数呀). 当时老是想去带吸收壁的随机行走问题去了, 其实就是同样问题的不同阐述而已, 不同阐述的Catalan问题还多着呢: 元素的进栈出栈具体有哪些方式, 列举n对括号的所有合法方式, 将凸多边形划分成三角形区域的方法等等, 举不胜举.
题目说演示, 那就把所有的可接受序列打印出来吧. 我想着想着就想到用递归实现, 不过当时根本没有划分清楚不同的状态: 比如售票员手中有几张5角的票, 还有几个五毛游客在排队等等. 因此没写明白. 回去捋了下, 果然用递归实现是很易理解的, 而且容易扩展: 比如再加几种类型的纸币也可以很容易修改程序使之继续可用(钱币类型多的话不是想象中的那么容易的). 不过缺点就是递归效率太低. 因为字符串 “10″ 太长了, 所以就用 “T” 表示手持1元的游客购票, 用 “F” 表示持有5角的游客购票. 把 “F” 替换成 “(“, 把 “T” 替换成 “)” 就是列出所有合法的括号配对方法了. 代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

static unsigned int COUNT = 0;

void sale_ticket(int num_of_push_fiftycents, int sum_of_fiftycents_instack, char *pstr, const int max_push)
{
    /*
     * 若钱箱内五角钱币为空, 且还有持有五角的游客排队时,则下次
     * 只能接受五角游客购票。
     */
    if (sum_of_fiftycents_instack == 0 && num_of_push_fiftycents < max_push) {
        strcat(pstr, "F");      /* 把F加到字符串尾部表示有五角进了售票员的钱箱 */
        sale_ticket(num_of_push_fiftycents + 1, 1, pstr, max_push);
    }

    /*
     * 若钱箱内五角钱币非空, 且还有持有五角的游客排队时, 则下次
     * 可以接受两种游客的购票要求。
     */
    if (sum_of_fiftycents_instack > 0 && num_of_push_fiftycents < max_push) {
        /* 情况1:下一个游客是1元党 */
        char *new_pstr;
        if ((new_pstr = malloc(2 * max_push + 1)) == NULL) {
            perror("sale_ticket: malloc");
            exit(1);
        }
        strcpy(new_pstr, pstr);
        strcat(new_pstr, "T");  /* 把T加到字符串尾部表示有五角出了钱箱, 即找零 */

        COUNT++;            /* 又多了一种可接受的排队方案 */
        sale_ticket(num_of_push_fiftycents, sum_of_fiftycents_instack - 1, new_pstr, max_push);

        /* 情况2:下一个购票的是五毛 */
        strcat(pstr, "F");
        sale_ticket(num_of_push_fiftycents + 1, sum_of_fiftycents_instack + 1, pstr, max_push);
    }

    /*
     * 当只剩下一元党在排队购票时,下一个购票的必然是一元的了
     */
    if (sum_of_fiftycents_instack > 0 && num_of_push_fiftycents == max_push) {
        strcat(pstr, "T");
        sale_ticket(num_of_push_fiftycents, sum_of_fiftycents_instack - 1, pstr, max_push);
    }

    /*
     * 当五毛入钱箱次数等于持有五毛游客的数量,
     * 且钱箱内五角钱币为空时, 表明到达了边界,
     * 则输出之前记录的买票顺序字符串. 并释放
     * 内存, 返回.
     */
    if (num_of_push_fiftycents == max_push && sum_of_fiftycents_instack == 0) {
        printf("%s\n", pstr);
        free(pstr);
        return;
    }
}

int main(int argc, char **argv)
{
    char *prt;
    int max_push;   /* 五毛游客数, 即五毛进钱箱的最大次数 */

    if (argc < 2) {
        fprintf(stderr, "usage: %s num\n", argv[0]);
        exit(0);
    }
    max_push = atoi(argv[1]);
    if (max_push > 0)
        COUNT++;
    if ((prt = malloc(2 * max_push + 1)) == NULL) {
        perror("main: malloc");
        exit(1);
    }
    memset(prt, 0, 2 * max_push + 1);

    sale_ticket(0, 0, prt, max_push);
    printf("The total number is: %d\n", COUNT);
    return 0;
}

输入程序名后加数字即运行. 设置的数字不要超过16, 根据卡特兰数的一般项公式: Cn = (2*n)!/((n+1)! n!) . 第16项为35357670, 把所有可接受的排队顺序字符串输出需要1.1G的空间! 在我机器上大概10秒才运行完. 输入5的情况见这里:http://codepad.org/VKu9Bbb8

最后一题问的是如何排查一个程序的瓶颈. 很惭愧我不知道.

下午项目面试: 走进X公司的内部, 里面环境出乎我意料: 像个饭馆, 看上去挺惬意的, 不过里面没有人逗留. 最后面试官问了如何用多路复用改进我目前接触的项目(面试官技术不错, 听我稍微介绍完项目后就看出用定时轮询的方式耗资源).只要和网络开发有关的一般都会问到多路复用: select和poll机制. 这次果然也是. 结果没答明白. 拿了盒冬瓜茶就被叫回去了.

 

看了这样的makefile


GNU make是个非常好的工具. 大部分Linux程序员都自己编写Makefile, 再利用make构建工程. 优秀的程序员编写层次分明的代码, 将它们放在合适的位置, 优雅地用make生成目标文件以及管理工程. 不过, 再好的工具和材料, 到了笨拙建造师手里, 也有可能造出丑陋的建筑. 我就看了这样一个例子.

这个工程共有 3 层目录: 顶层有一个makefile. 要编译某个平台的目标文件首先要进入这个以这个平台名字命名的子目录, 比如 “Linux-x86-xx”. 子目录”Linux-x86-xx”内也有个makefile, 打开一看, 部分语句是这样的:

# makefile
compilevos: generateconfigfiles
		$(MAKE) -f ../../makefile xos.o
generateconfigfiles: runtailor
runtailor:
	...

发现上两层目录已经不属于这个工程了. 又看了下readme才知道要编译目标得先运行这个”Linux-x86-xx”目录里面的一个脚本”makelib”, 部分语句是这样的:

# makelib
cd obj
make -f ../makefile compilelibs
cd ..

先进入”obj”这个子目录, 这样”makefile” 里面的”$(MAKE) -f ../../makefile xos.o” 其实是调用上层目录的makefile了. 看得我都吐血了. 往下看发现依然现象严重, 写这个makefile的哥儿们竟全用名字诸如”makeobj”, “makexox”, “maketarget”等脚本调用make的, 关键是在脚本里每次调用之前都先cd到另外一个目录里, 在makefile根本不知道这条命令是在哪个目录里执行的. “goto”都不能比肩.

这么好的make就这样给糟蹋了.


« vorherige Seite