指针类型转换与编码风格
本帖最后由 guew 于 2013-4-17 02:53 编辑最近在做一个项目,把函数之间的数据传递都统一成了指针的传递。
小可在小公司谋职,能讨论交流的人较少,所以就斗胆把想法放在这里,和大伙讨论讨论这种方式的利弊。
1. 基本概念
a)取指针的内容:p是一个指针,*p或者p可以取指针上内容。
b)指针类型转换:p是一个4个字节长度的指针,(unsigned char *)&p可以将p的3个高字节删除,只保留一个低字节。用p可以取到转换前4字节指针指向内容的 byte0, p 可以取到转换前指针指向内容的byte1,p为byte2,p为byte3。此方法也适用于其他长度的指针。
2.基于指针的函数
void universal_converter(unsigned char *pi, unsigned char *po)
{
po = pi;
po = pi;
po = pi;
po = pi;
}
void mian()
{
float a,b;
unsigned int c,d;
unsigned char e;
a = 8.88;
c = 1521;
universal_converter((unsigned char *)&a, &e);
universal_converter(&e, (unsigned char *)&b);
universal_converter((unsigned char *)&c, &e);
universal_converter(&e, (unsigned char *)&d);
printf("%f\n%d", b,d);
}
3. 好处
a)用户可以将任何数据类的控制范围都扩展到一个字节。
b)函数与函数之间只有单向耦合关系。
c)简化和统一编程方式。
记号下,等大大们养肥了慢慢看。。 入参一定要检查啊,这种方法有风险啊 使用指针需要非常清楚的知道你自己在干嘛。
强制转换指针需要注意,转换后的数据类型长度不要超过原有的,除非你故意这么用。 第一点,截取四字节变量的低字字,需要考虑编译器是大端模式还是小端模式吧 liangbmw 发表于 2013-4-17 07:34 static/image/common/back.gif
入参一定要检查啊,这种方法有风险啊
说的对 别把心思放这里 多注意可读性 这是小众思维 和坏处相比,好处就看不见了 有些想法,不过正如楼上所言,和坏处相比,好处就看不见了 本帖最后由 monkerman 于 2013-4-17 09:20 编辑
1. 指针作参数一定要检查啊. 未赋值的引用, 传递的是数组(编译器并不进行边界检查)都容易出问题, 尽量多的返回或打印有用的信息. 不然出问题会莫名其妙的. 调起来蛋疼. 安全的方法就是顺带传入操作数据的长度.
2. 如果传入的指针, 程序并不打算修改他, 为了安全, 那就在前面用 const 修饰.
3. 还有就是, 强制转换类型时, 取到的是最低字节, 这只对小端模式有作用. 那你就要考虑会不会出现大端模式的情况, 指针的大小会不会不同? 也就是可移植性, 如果否, 那用联合就更简便了.
4. 如果指针不能使程序接口更清晰, 更容易理解, 更易维护, 更优雅. 其他方式也许更好呢? 指针不是万能的.
5. 指针的效率是在要传入和处理的数据比较大时(如: 数组, 结构体等...)体现出来的, 如果数据比较小和少, 直接传值反而更好更简单.
6. 简单易理解的才是最好的. 程序首先是给人看的, 别让后面接手的同事骂娘.{:titter:}
7. 欢迎补充讨论, 应该还有一些. 暂时只想这么多.
关于指针容易引起的错误: 可参考这个帖子: http://www.amobbs.com/thread-5499800-1-1.html 就一会, 这么多回复.{:lol:}
想到一个笑话: C 语言不会指针, 那是 C--.{:titter:} 8位机用指针效率低。 本帖最后由 dr2001 于 2013-4-17 09:16 编辑
该方法没有什么好处。
1、基于字节来操作基本数据类型,可能导致内存布局,对齐,类型转换等方面的隐含错误。除非是为了解决这些问题而进行的操作,否则其本身是高风险且不可移植的。
2、C作为静态类型语言功能之一就是类型检查,强制转换就没了。不合理的强制类型转换可能引发上述隐含的问题。
3、C的数据类型本身长度和平台以及编译器是相关的,这么干出来的代码移植性几乎没有。
除非是数据持久化、格式化、基于包的数据处理等应用,否则这么干只是在增加错误而已。 dr2001 发表于 2013-4-17 09:08 static/image/common/back.gif
该方法没有什么好处。
1、基于字节来操作基本数据类型,存在内存布局,对齐,类型转换等方面的隐含错误。 ...
+1 学习. leijiayou 发表于 2013-4-17 08:41 static/image/common/back.gif
说的对 别把心思放这里 多注意可读性 这是小众思维
“入参检查”是什么意思? monkerman 发表于 2013-4-17 09:04 static/image/common/back.gif
1. 指针作参数一定要检查啊. 未赋值的引用, 传递的是数组(编译器并不进行边界检查)都容易出问题, 尽量多的 ...
第2点:“为了安全”,具体体现在哪里?
第3点:如果是大端模式,强制类型装换后,是不是取到了高处的字节,即p为byte3?那么,p为byte2,还是内存某处的任意数据?
如果是前者,那么对应关系还存在。再者,如果如果从小端模式移植到大端模式,那么位移操作是不是也要改过?如果要改,那么用指针的程序在移植上具体会多出什么麻烦?
小弟不才,望前辈多多指教。
guew 发表于 2013-4-17 10:49 static/image/common/back.gif
第2点:“为了安全”,具体体现在哪里?
第3点:如果是大端模式,强制类型装换后,是不是取到了高处的字 ...
我可不是前辈哈. 一起交流讨论.
2. 安全体现在, 你例程中的 pi 指针, 实际上你只是读取地址内的值, 并不打算对其进行修改/写操作. 那就该加上 const, 首先是清晰的告诉程序员这个指针是不能修改的, 再者因为一旦函数内的实现复杂点, 有点绕的时候, 难保自己不做出傻事来, 将本不该修改的数据给修改了. 导致其他地方对这个地址的值引用时出现偏差或错误. 更严重的是如果以后有同事接手, 难保他像你一样小心谨慎.
3. 大端模式时, p 对应了数值的高字节, 顺序不是和你原来的实现反了么? 还是会取连续的字节数据, P 对应 Byte3, P 对应 Byte0....
另外就是位操作的问题, 这牵涉到位序, 现在一般的大小端模式指的都是字节序, 最小的粒度是字节. 即高低地址对应的是低高字节还是高低字节,
而小端模式下, 都是 LSB 0 位序( 0x80 对应 1000 0000). 而大端模式下就不好说了, 可以是 MSB 0(0x80 对应 0000 0001), 也可能是 LSB 0. 得看具体的手册. 本帖最后由 guew 于 2013-4-17 15:00 编辑
monkerman 发表于 2013-4-17 13:51 static/image/common/back.gif
我可不是前辈哈. 一起交流讨论.
2. 安全体现在, 你例程中的 pi 指针, 实际上你只是读取地址内的值, 并不 ...
第3点中,和小端模式相比,在大端模式下,数据反着进函数,又反着出函数,负负得正,最后还是能装成原来的数据类型(这里是float和uint32),所以这个实例的功能还是一样的。但如果实例是把4个字节的float拆成4个uint8,然后逐一送串口发送,这样的确会造成发送顺序的颠倒。但如果用位移操作来拆float,由于大小端的变化,同样会造成这个问题,与是否用指针无关。这种用指针来定位数据子元素(如float的某个byte)方式主要是用来代替位移操作,其更加的简明直观。
所以,大小端变化造成的问题,不管是否用指针都是一样存在的? guew 发表于 2013-4-17 14:56 static/image/common/back.gif
第3点中,和小端模式相比,在大端模式下,数据反着进函数,又反着出函数,负负得正,最后还是能装成原来 ...
我觉得你的理解是对的,但是觉得你程序不应该这样写,晦涩难懂,可能是我水平不高吧,我还是觉得用联合体好些。 guew 发表于 2013-4-17 14:56 static/image/common/back.gif
第3点中,和小端模式相比,在大端模式下,数据反着进函数,又反着出函数,负负得正,最后还是能装成原来 ...
在你这个例子中, 操作是对称的, 正好是负负得正.......而不是针对其中的一部分进行操作. 所以.....并不会和大小端冲突. 那要是非对称的呢?
抱歉, 我不知道你这么做的目的是什么? 恕我直言, 没感觉到多简明直观. 反而很复杂.{:shutup:} 绿茶山人 发表于 2013-4-17 15:26 static/image/common/back.gif
我觉得你的理解是对的,但是觉得你程序不应该这样写,晦涩难懂,可能是我水平不高吧,我还是觉得用联合体 ...
嗯,跟个人习惯有关吧,这样操作让我有种脚踏实地的感觉,可以和内存中的位置直接联系起来(比方说1个byte就是8个cmos),从而跳过了unsigned int,float这类抽象的东西。 本帖最后由 guew 于 2013-4-17 16:15 编辑
monkerman 发表于 2013-4-17 15:57 static/image/common/back.gif
在你这个例子中, 操作是对称的, 正好是负负得正.......而不是针对其中的一部分进行操作. 所以.....并不会 ...
在非对称的情况下,用位移操作也会有同样的问题,和指针无关?
可能是个人习惯问题,这样做给我一种实实在在的感觉。
还有就是简化编程(是我的错觉?)
这样做的话,程序中函数就只有4种形式(这里以uint8为例,可以是任何类型):
void fun ()//无输出,输出
void fun(uint8 *pi, uint8 *pii, uint8 *piii ……)//只有n个输入
void fun(uint8 *pi……, uint8 *po……)//n个输入与输出
void fun(uint8 *po, uint8 *poo, uint8 *pooo……)//只有n个输出
给我的感觉,楼主是玩汇编多过C? Louis_Bright 发表于 2013-4-17 16:17 static/image/common/back.gif
给我的感觉,楼主是玩汇编多过C?
以前做过一个乘法器芯片的layout,所以这么写c可能和那时的印象关联上了。 本帖最后由 guew 于 2013-4-17 17:03 编辑
monkerman 发表于 2013-4-17 09:04 static/image/common/back.gif
1. 指针作参数一定要检查啊. 未赋值的引用, 传递的是数组(编译器并不进行边界检查)都容易出问题, 尽量多的 ...
你提到的“顺带传入操作数的长度”,可能会使代码看起沉长。
如:
void float_to_byte(float *pi, unsigned char *po)
{
(unsigned char *)pi;
po = pi;
po = pi;
po = pi;
po = pi;
}
void byte_to_float(unsigned char *pi, float *po)
{
(unsigned char *)po;
po = pi;
po = pi;
po = pi;
po = pi;
}
void mian()
{
float a,b;
unsigned char e;
a = 8.88;
float_to_byte(&a,&e);
byte_to_float(&e,&b);
printf("%f\n%d", b);
}
再者,可能也会破坏pi,po都是同一类型所带来的对称性,表现在在这里就是一个函数就能进行对称装换。
本帖最后由 monkerman 于 2013-4-17 17:50 编辑
guew 发表于 2013-4-17 17:02 static/image/common/back.gif
你提到的“顺带传入操作数的长度”,可能会使代码看起沉长。
如:
void float_to_byte(float *pi, un ...
你这样的操作, 如果是我一般是不会使用指针的. 我首先选联合.// 联合
typedef union {
floatfloat_Val;
unsigned charbytes;
} byte2float; // 这样不是省掉两个函数??????
传递指针要操作数据的长度还是为了安全. 也许你看起来多此一举, 代码冗余. 我不这么认为.可以传入sizeof(float/double)/sizeof(int/long)......当然你这个函数就一项功能. 除了检查参数, 传入长度确实没必要.
现在新的 C 库, 有关指针操作数据的情况, 好多都增加了长度作为参数来提高可靠性.
我上面说过, 指针一般都在处理大数据/包时才能体现高效的一面. monkerman 发表于 2013-4-17 17:43 static/image/common/back.gif
你这样的操作, 如果是我一般是不会使用指针的. 我首先选联合.传递指针要操作数据的长度还是为了安全. 也 ...
嗯,对的,对的。我这样写只是旁门僻道,真正要写的正派大气还是需要按你说的那样写。
兄台在这方面有没有好的书推荐推荐哈? guew 发表于 2013-4-17 22:05 static/image/common/back.gif
嗯,对的,对的。我这样写只是旁门僻道,真正要写的正派大气还是需要按你说的那样写。
兄台在这方面有没 ...
你这么说, 搞的我怪不好意思的.
书介绍的话挺多的, 工作了估计也来不及看完. <深入理解计算机系统><C程序设计语言><C和指针><C专家编程><C陷阱与缺陷><C Primer Plus>还有正在看的<Unix 环境高级编程><Unix编程艺术>......不推荐看国内的书. 英语好的话就看原版吧. 我英语很差, 只能看翻译的了.{:dizzy:}
我觉得应该多看看高手写的源代码, 亲自实现其中的一部分, 加以修改, 验证想法.
其实最好的方法是用 google 搜索关键字. 会有很多结果, 点进去一一看完, 关于这方面的知识点也就差不多了. 但是得需要这些知识点的术语词汇. 不然没法搜.
或者到 CSDN 论坛 C 板块搜已解决的帖子, 精华帖什么的, 高手很多, 很受用. 能学到不少技巧.
再或者就是逛各种博客了. 那里高手也很多的.
我还不是高手, 共同努力进步吧. {:handshake:} 我大概明白LZ为什么要如此处理。
比如如下的应用,对于上层软件,需要处理的是一个4字节的LONG型数组,而这个数组要通过UART送出,或保存在eeprom中。后面的处理只能一个一个字节的处理。所以LZ想出了这个指针类型强制转换的方法。
首先是这个方法是可行的,优点是节省存储空间,减少数据拆装的处理过程,不需要考虑什么大头、小头的问题,程序效率高。缺点是强制转换指针类型容易出错,程序不易读懂。
建议使用共同体数据结构(UNION),效果相同,不容易出错,程序易懂。这个在上面已经有朋友提到了,我在N年前就使用,也是标准的方法。共同体也有叫联合体的,一个意思,C中定义这样的数据类型就是为此种用途使用的,一般情况下最好不要采用非常规的方法。 本帖最后由 guew 于 2013-4-19 03:13 编辑
machao 发表于 2013-4-18 22:05 static/image/common/back.gif
我大概明白LZ为什么要如此处理。
比如如下的应用,对于上层软件,需要处理的是一个4字节的LONG型数组,而 ...
这个union好生了得啊!和位域配合使用,就可以将用户的数据控制能力扩展到比特级别!(刚刚发现的,大牛看了别笑话哈~)#include <stdio.h>
#define cut (uint8 *)
typedef unsigned char uint8;
typedef struct
{
uint8 byte0_0:1;
uint8 byte0_1:1;
uint8 byte0_2:1;
uint8 byte0_3:1;
uint8 byte0_4:1;
uint8 byte0_5:1;
uint8 byte0_6:1;
uint8 byte0_7:1;
uint8 byte1_0:1;
uint8 byte1_1:1;
uint8 byte1_2:1;
uint8 byte1_3:1;
uint8 byte1_4:1;
uint8 byte1_5:1;
uint8 byte1_6:1;
uint8 byte1_7:1;
uint8 byte2_0:1;
uint8 byte2_1:1;
uint8 byte2_2:1;
uint8 byte2_3:1;
uint8 byte2_4:1;
uint8 byte2_5:1;
uint8 byte2_6:1;
uint8 byte2_7:1;
uint8 byte3_0:1;
uint8 byte3_1:1;
uint8 byte3_2:1;
uint8 byte3_3:1;
uint8 byte3_4:1;
uint8 byte3_5:1;
uint8 byte3_6:1;
uint8 byte3_7:1;
} type_bit;
typedef union
{
type_bit this_bit;
float this_float;
} type_cast;
void main()
{
type_cast this_cast;
this_cast.this_float = 6.66;
printf("%x %x %x %x\n",(cut&this_cast),(cut&this_cast),(cut&this_cast),(cut&this_cast));
printf("%d%d%d%d%d%d%d%d %d%d%d%d%d%d%d%d %d%d%d%d%d%d%d%d %d%d%d%d%d%d%d%d\n",\
this_cast.this_bit.byte3_7,\
this_cast.this_bit.byte3_6,\
this_cast.this_bit.byte3_5,\
this_cast.this_bit.byte3_4,\
this_cast.this_bit.byte3_3,\
this_cast.this_bit.byte3_2,\
this_cast.this_bit.byte3_1,\
this_cast.this_bit.byte3_0,\
this_cast.this_bit.byte2_7,\
this_cast.this_bit.byte2_6,\
this_cast.this_bit.byte2_5,\
this_cast.this_bit.byte2_4,\
this_cast.this_bit.byte2_3,\
this_cast.this_bit.byte2_2,\
this_cast.this_bit.byte2_1,\
this_cast.this_bit.byte2_0,\
this_cast.this_bit.byte1_7,\
this_cast.this_bit.byte1_6,\
this_cast.this_bit.byte1_5,\
this_cast.this_bit.byte1_4,\
this_cast.this_bit.byte1_3,\
this_cast.this_bit.byte1_2,\
this_cast.this_bit.byte1_1,\
this_cast.this_bit.byte1_0,\
this_cast.this_bit.byte0_7,\
this_cast.this_bit.byte0_6,\
this_cast.this_bit.byte0_5,\
this_cast.this_bit.byte0_4,\
this_cast.this_bit.byte0_3,\
this_cast.this_bit.byte0_2,\
this_cast.this_bit.byte0_1,\
this_cast.this_bit.byte0_0);
this_cast.this_bit.byte0_0 = 1;
this_cast.this_bit.byte0_1 = 1;
this_cast.this_bit.byte0_2 = 0;
this_cast.this_bit.byte0_3 = 1;
this_cast.this_bit.byte0_4 = 1;
this_cast.this_bit.byte0_5 = 1;
this_cast.this_bit.byte0_6 = 1;
this_cast.this_bit.byte0_7 = 0;
this_cast.this_bit.byte1_0 = 0;
this_cast.this_bit.byte1_1 = 0;
this_cast.this_bit.byte1_2 = 1;
this_cast.this_bit.byte1_3 = 0;
this_cast.this_bit.byte1_4 = 1;
this_cast.this_bit.byte1_5 = 0;
this_cast.this_bit.byte1_6 = 0;
this_cast.this_bit.byte1_7 = 0;
this_cast.this_bit.byte2_0 = 0;
this_cast.this_bit.byte2_1 = 1;
this_cast.this_bit.byte2_2 = 1;
this_cast.this_bit.byte2_3 = 1;
this_cast.this_bit.byte2_4 = 0;
this_cast.this_bit.byte2_5 = 0;
this_cast.this_bit.byte2_6 = 0;
this_cast.this_bit.byte2_7 = 0;
this_cast.this_bit.byte3_0 = 1;
this_cast.this_bit.byte3_1 = 0;
this_cast.this_bit.byte3_2 = 0;
this_cast.this_bit.byte3_3 = 0;
this_cast.this_bit.byte3_4 = 0;
this_cast.this_bit.byte3_5 = 0;
this_cast.this_bit.byte3_6 = 1;
this_cast.this_bit.byte3_7 = 0;
printf("%f",this_cast.this_float);
} 如果能把指针强制转换到比特就完美了。 本帖最后由 guew 于 2013-4-19 12:53 编辑
guew 发表于 2013-4-19 02:57 static/image/common/back.gif
这个union好生了得啊!和位域配合使用,就可以将用户的数据控制能力扩展到比特级别!(刚刚发现的,大牛 ...
上午屁颠屁颠的把这个“发现”告诉了俺师傅,他说TI的DSP库(28335)就是这么做的,看了一下还真是这样,用位域和联合把寄存器都打散到了比特级别,然后一个一个赋值就可以了。
这样一来,软件就很好分层了,我想是不是可以这样写?:
顶层:用户
如:
timer_init(哪个timer,工作形式,初始赋值…)
————————————————————————
中间层:设备功能
如:
timer_init(哪个timer,工作形式,初始赋值……)
{
……
timer.timer0.TCCR.cs00 = xxxx;
timer.timer0.OCR0 = xxxx;
……
}
————————————————————————
底层:寄存器操作
用位域和联合将寄存器打散到比特级
如果再利用结构体中数据地址连续的特点,先给头个寄存器做mapping,然后余下的寄存器大家排好队,依次入结构就OK了!
我这么想对不对?过来人能不能谈谈这方面的看法?
本帖最后由 guew 于 2013-4-19 12:46 编辑
有个奇怪的发现(可能我看的还不够多):
从8位AVR——》stm32的M3——》TI的DSP
越是往后,原厂提供的库就越是明细。AVR基本没库?,stm32只提供到设备功能级别,没有打散寄存器,TI一直深入到寄存器的位。
这个现象有木有普遍性?如果有,又是为了那般?
mcu_lover 发表于 2013-4-17 08:31 static/image/common/back.gif
使用指针需要非常清楚的知道你自己在干嘛。
强制转换指针需要注意,转换后的数据类型长度不要超过原有的, ...
龙哥,受”除非你故意这么用的“启发,这个float与byte互转的函数还可以再精简一点:#include <stdio.h>
#define cut (uint8 *)
#define blow (float *)
typedef unsigned char uint8;
void universal_converter(uint8 *pi, float *po)
{
po = (blow pi);
}
void main()
{
float a,a_reborn;
uint8 c;
a = 8.88;
universal_converter(cut &a,blow c);
universal_converter(c,&a_reborn);
printf("%f\n",a_reborn);
} 其实C++的什么private和public以及结构体等等,就是为了避免c的变量过于零散,还有就是想lz想法这样,变量交换信息的时候容易出错{:2_35:} goolloo 发表于 2013-5-6 22:19 static/image/common/back.gif
其实C++的什么private和public以及结构体等等,就是为了避免c的变量过于零散,还有就是想lz想法这样,变量 ...
“出错”是指人容易误读误操作么? 本帖最后由 machao 于 2013-5-8 19:44 编辑
guew 发表于 2013-4-19 12:45 static/image/common/back.gif
有个奇怪的发现(可能我看的还不够多):
从8位AVR——》stm32的M3——》TI的DSP
越是往后,原厂提供的库就 ...
1,芯片的功能越来越强
2,真正懂硬件的工程师越来越少
3,厂家为了更好的适应这样的实际情况,就给你一个假象:“性能越强的芯片使用越方便”,从而扩大市场。
浮躁世界中的必然做法
========================
补充一点,厂商的库只是提供一个通用的基本的使用,简单使用或者参考学习都可以,要真正发挥芯片的功能,设计出功能可靠、性价比高的产品广靠长家的库是做不到的。厂家的库充其量也就是一些稍微好一些的硕士生的实习代码或毕业设计。
学习!!!! 我也很欣赏TI DSP库的做法。
页:
[1]