首页 > 学技术 > 技术网文 > C/C++ > 正文

[原创] 简单小代码:不用计算、不用位操作来实现 加 1


来源 chinaunix.net kuqin整理

[color=Blue]特此说明,这点小技俩,仅供娱乐~[/color]

用位操作虽然可以实现加1,但似乎还得要 循环 来帮助。
最简单的事情莫过于让 [color=Red]编译器[/color] 来帮你实现 加 1 功能。

int _inc(int i)

{
      char (*p)[2] = (char (*)[2])i;
     
      return (int)&((*p)[1]);
}

 
int main()
{
      printf("%d\n", _inc(100));
      
      return 0;
 
}
 
----------------------------
 
当然可以更简单点,直接定义为宏:
 
#define _INC(x)            (int)&((*((char(*)[2])x))[1])


[ 本帖最后由 mik 于 2006-11-20 23:20 编辑 ]



 mik 回复于:2006-10-06 11:44:53

让它来得更通用一点:

#define _INC(x) (                                    \
    { typeof(x) _t = x;                               \
     (int)&((*((char(*)[2])_t))[1]); })


 emacsnw 回复于:2006-10-06 15:03:49

这个有点意思,a[1] 其实就是 *(a+1),不过这样也算不用加法吗?


 mik 回复于:2006-10-06 15:07:57

呵呵,这些计算是在编译器层面上,编译代码时,编译器计算的,用户代码没有计算这一步


 whyglinux 回复于:2006-10-06 15:48:23

1. 似乎不是实现的加 1 功能,而是加 2,因为 p 指向一个具有两个char元素的数组,所以 p 和 p+1 之间的距离是 2 字节。要实现加 1 应该使用长度为 1 的字符数组。

2. 按照你这个思路,_inc 函数的实现还可以更简单些,即使 p 指向单个字符,而不是一个字符数组:

      char* p = (char*)i;
      return (int)&p[1]; /* 相当于 return (int)(p + 1); */

3. 无论是你程序中的 &(*p[1]),还是上面的 &p[1],都需要 [color=red]计算[/color] p + 1 的值。由于 p 是一个指针变量,不是常量,所以这个计算只能在 [color=red]运行时[/color] 计算,不可能是一个 [color=red]编译时[/color] 概念。


 mik 回复于:2006-10-06 16:09:13

引用:原帖由 whyglinux 于 2006-10-6 15:48 发表
1. 似乎不是实现的加 1 功能,而是加 2,因为 p 指向一个具有两个char元素的数组,所以 p 和 p+1 之间的距离是 2 字节。要实现加 1 应该使用长度为 1 的字符数组。

2. 按照你这个思路,_inc 函数的实现还可以更 ... 




不要这么快下结论,你试过了吗?

1、

[color=Blue]p 是一个 char (*)[2] 型指针, 那么 *p 类型是: char [2][/color]
char c[2];

p = & c;

*p 就是 c,是一个 char[2];

(*p)[1] 相当于 c[1]

那么,c[1] 相对于 c[0],你说是不是 偏移了 1 byte  呀?


2、数组之间的偏移,不是编译时确定,难道是运行时确定 ??

[ 本帖最后由 mik 于 2006-10-7 22:28 编辑 ]


 whyglinux 回复于:2006-10-06 16:19:59

>> (*p)[1] 相当于 c[1]

那你应该这样使用:(int)&((*p)[1]),而不是上面程序中的 (int)&(*p[1])。


 mik 回复于:2006-10-06 16:34:19

引用:原帖由 whyglinux 于 2006-10-6 16:19 发表
>> (*p)[1] 相当于 c[1]

那你应该这样使用:(int)&((*p)[1]),而不是上面程序中的 (int)&(*p[1])。 



那确实是手误, 宏定义 _INC(x) 里就正确了


 lixuda 回复于:2006-10-06 16:36:36

自己测试下,whyglinux 是对啊。
也简单
没有必要用到(*p)[]吧。。
不过楼主厉害


 awake 回复于:2006-10-06 16:52:36

引用:原帖由 mik 于 2006-10-6 16:09 发表

2、数组之间的偏移,不是编译时确定,难道是运行时确定 ??




可惜这个数组本身不是静态时确定的。


 converse 回复于:2006-10-06 17:11:17

加精鼓励一下.

mik耍了一把编译器,把需要递增的值作为数组地址传给p,然后求以这个地址开始的下一个元素也就实现了加一的目的了,whyglinux那样可能更好看懂一些的,原来的做法麻烦了一点.


 mik 回复于:2006-10-06 17:18:09

引用:原帖由 awake 于 2006-10-6 16:52 发表


可惜这个数组本身不是静态时确定的。 






在 _inc(100);

这种情形下,是编译器计算的:

	movl	$101, 4(%esp)

movl $.LC0, (%esp)
call printf






在另一种情形下,还是要运行时确定:
int i = 0;
printf("%d\n", _inc(i));

movl        -4(%ebp), %eax

incl          %eax
movl        %eax, 4(%esp)
movl        $.LC0, (%esp)
call printf



 awk就是awp加ak 回复于:2006-10-06 17:38:51

呵呵,地址加1 !


 win_hate 回复于:2006-10-06 20:36:55

a c99 way

#include <stdio.h>

f(int n)
{
struct {
char a[n];
char b;
} *a;
printf ("%d\n", sizeof(*a));
}

main (int argc, char **argv)
{
f(atoi(argv[1]));
}




./a.out 1234
1235


[ 本帖最后由 win_hate 于 2006-10-6 20:38 编辑 ]


 lovesaka 回复于:2006-10-06 23:17:34

引用:原帖由 win_hate 于 2006-10-6 20:36 发表
a c99 way

#include <stdio.h>

f(int n)
{
struct {
char a[n];
char b;
} *a;
printf ("%d\n", sizeof(*a));
}

main (int argc, char **argv)
{
f(atoi(argv[1 ... 


这也给想出来了强!!!!


 converse 回复于:2006-10-06 23:35:31

win_hate老大的代码如果考虑struct对齐的情况下呢?似乎还要加一个编译选项指明是严格的大小,而不是对齐之后的大小.


 win_hate 回复于:2006-10-06 23:58:11

印象里会 gcc 按结构成员中最大的``成员对齐'' 要求来对齐的。这里全是  char,应该不会有问题。加个对齐指令会保险一些。

但我总是记不住对齐指令,要 google,所以就算了。


 flw 回复于:2006-10-07 22:18:07

想不通为什么要 [2]
int _inc(int i)

{
      char *p = (char *)i;
     
      return (int)&(p[1]);  // 我一般不写 &(p) 而直接写 p+i; 所以其实说白了还是 p+1
}


int main()
{
      printf("%d\n", _inc(100));
      
      return 0;

}


[ 本帖最后由 flw 于 2006-10-7 22:21 编辑 ]


 mik 回复于:2006-10-07 22:27:32

脑子清醒了,是 OK

看,抛出了砖头,把玉给引来了 :)


 yjh777 回复于:2006-10-08 10:45:06

都是吃饱了撑的,就一个加1操作需要不用+/++操作符,非要调个函数,还有竟然用数组指针的。
把你们的“钻研精神”用到其他地方行不?中国的软件精英们

[color=Yellow]printf("%d\n", _inc(100)); 
printf("%d\n", 100+1);
printf("%d\n", 101);[/color]


 美丽人生 回复于:2006-10-08 11:11:58

如果是个常量
比如
const int p=100;
那么printf("%d",p+1);
好的编译器加一也是在编译时完成的

如果不是常量,那么,你再怎么耍巧,那都得运行时计算的
可以想象一下,被加数都还不知道,编译器又怎么呢做加法呢?


有句不是很雅的话,说的是脱了裤子打屁,多此一举


 庄周蝴蝶 回复于:2006-10-08 11:41:52

template <int a>
int Inc()
{
    return a + 1;
}


......

std::cout << Inc<100>() << endl;


 飞灰橙 回复于:2006-10-08 13:50:35

的确多此一举,包括楼上用template的方式也是多此一举(template要求int a为常量)。
#define INC(x)  ((x) + 1) 可以胜任楼上任何一种代码。


 awake 回复于:2006-10-08 14:58:31

Hacker们喜欢这种方式啊。可以增加我们阅读代码的能力,而且有时候可以学到点东西。写这些代码当然不是为了优化+1。


 ccjjhua 回复于:2006-10-08 17:02:20

不知道long long 型的+1用楼主的方式能不能做到。就是说被加数如果很大,超出了该进程允许访问的地址范围,这种方式+1 不会有问题吗?


 ccjjhua 回复于:2006-10-08 17:05:25

我觉得铁定出问题!!!


 awk就是awp加ak 回复于:2006-10-08 17:55:23

其实这不是为了效率,也不是考虑方便、易读性。主要是让思维开阔,看脑子会不会转弯 :em02:


 awk就是awp加ak 回复于:2006-10-08 17:59:45

引用:原帖由 ccjjhua 于 2006-10-8 17:02 发表
不知道long long 型的+1用楼主的方式能不能做到。就是说被加数如果很大,超出了该进程允许访问的地址范围,这种方式+1 不会有问题吗? 





 langue 回复于:2006-10-08 18:11:03

引用:原帖由 yjh777 于 2006-10-8 10:45 发表
都是吃饱了撑的,就一个加1操作需要不用+/++操作符,非要调个函数,还有竟然用数组指针的。
把你们的“钻研精神”用到其他地方行不?中国的软件精英们

[_color=Yellow]printf("%d\n", _inc(100)); 
 ... 



开发智力么,总要有思想的摩擦。


 awk就是awp加ak 回复于:2006-10-08 20:58:17

对哑,我也不知道为什么要[2],正常思维应该是酱紫滴吧:
#define INCR(x) (int)&((char *)x)[1]


 chenwandong 回复于:2006-10-09 11:07:20

让人一眼就看懂的代码才是好代码


 chenwandong 回复于:2006-10-09 11:10:34

殊途同归,当时不应该提倡


 LinuxServer 回复于:2006-10-10 02:04:44

没劲。


 jruv 回复于:2006-11-20 22:05:38

真不知这样做有何意义,就像去研究“回”字有几种写法一样,其实这种方法比x+=1只是真加了复杂度,并且翻成最后的机器指令后,可能要去访问物理内存, 反而降低了运行效率,并且lz所谓的这样做是编译器计算的,不是用户代码计算的结论不知从何而来的。指针偏移也要进行计算的。


 mik 回复于:2006-11-20 22:57:59

晕~~ 忍不住想说几句,

明眼的人一看就知道:这点小技俩,只不过是写了玩玩,仅供娱乐娱乐

不知道怎么会有这么多人认真批判这点简陋的写作。


 chjcpu1 回复于:2006-11-21 09:00:44

lz不是在发帖时说明只供娱乐了吗,如果这也批评,恐怕将来没人敢发帖了。


 fibbery 回复于:2006-11-21 11:34:20

批判前考虑一下你要批判的是什么!


 zhirong_xu 回复于:2007-01-18 09:39:07

用来研究还是不错的啊,从小的地方着手分析是可以做大文章的,鼓励一下!


 nickzou 回复于:2007-01-18 12:11:21

就算编译器被骗过了,CPU呢,CPU执行++的指令要比这一串快多了。


 newzy 回复于:2007-01-18 13:59:32

把程序中添加一句, 用 gcc 生成汇编对比分析下:

int _inc(int i)
{
      char (*p)[2] = (char (*)[2])i;
     
      return (int)&((*p)[1]);
}



生成的汇编代码:

                ...
movl %eax, -4(%ebp)          
movl $0, -8(%ebp)                // 这两句, 初始化 i=0
leal -8(%ebp), %eax
incl (%eax)                         // 这两句, 实现 i++
movl -4(%ebp), %eax
incl %eax                           // 这两句, 实现 (int)&((*p)[1]);
leave
ret


[color=Red]对比不难发现, 它们被编译成相同的汇编语句, 即操作方法都是一样的.
(int)&((*p)[1]) 并未精简任何操作.
[/color]
所谓, "编译器运算" 到头来还是运行时计算的.

不过, 还是很恭喜楼主能有这个创新的想法.


 newzy 回复于:2007-01-19 17:34:19

在 arm 和 ppc 测试得到类似结果.


 okyzx 回复于:2007-02-24 11:05:27

lz想法很有创意


 ddvv 回复于:2007-04-16 20:05:06

LZ想法有意思~
顶一下




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=837603
转载请注明作者名及原文出处



收藏本页到: