桃源谷

心灵的旅行

人生就是一场旅行,不在乎旅行的目的地,在乎的是沿途的风景和看风景的心情 !
posts - 32, comments - 42, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理
re: 偶见CSDN的VC6的猛人 lymons 2010-05-05 11:18
@战魂小筑
不得不说,事实确实如此。
使用VC6,微软并不会查你,
不管你要钱,不就是不收费嘛。

#不要忘了,10年前的时候微软的策略就是VC6有标价,
但不收费,你随便用,目的不说你也清楚。
我觉得只要理解指针的概念就明白这是怎么回事儿了。
指针变量的内容,和指针指向的内容是两码事儿。
指针本身也是一个变量,它的内容存放的是它所指向空间的地址。
想要让指针指向别的地方,就得把这个指针变量的内容赋值上
这个地方的地址即可。

函数在进栈的时候,实参变量的内容 会被原样复制到函数栈上。
也就是说,指针传到函数里是它指向内容的首地址,其他变量传到函数里是它的值。在函数里改变参数的内容,只会影响栈上的变量的内容, 不会影响调用者传进来的实参。
所以,在函数里改变指针指向空间的内容,是没有问题的,因为你知道这个空间的地址。
要是改变指针指向的地址,也就是让他指向别的地方,则只会改变栈上的那个指针所指向的地址,也就是改变的是存放在栈上的地址,而非调用者传进来的实参。
因此,想要改变调用者的指针指向的地址,就得把调用者的指针的地址(指针本身也是一个变量,它也有地址)传到函数里去。

要么把形参写成二级指针(**),要么写成指针地址(*&). 后者的方式C不支持。
re: 偶见CSDN的VC6的猛人 lymons 2010-05-05 09:46
话也不尽然,
任何事情的存在都它存在的道理。
不能因为高效的新工具的出现,就否定老工具存在的价值。

举一个最简单的例子,VC6存在的一个典型的理由:
VC6免费,VC2003以上价值不菲,对于一个遵纪守法的
中小软件企业,在不需要高级应用的情况下,是不会批量购买
收费的版本的。

在公司眼里,时常要衡量的是提升效率所带来的成本,是否能得到
相应的收益。

这不像个人使用,即使使用了盗版的开发工具,也不会有人查你,
商业公司则不一样,如果使用了这些软件,每天都有人盯着你。
很棒,学习了。

不过,这对单个block的分配是个很好的解决方案,
但是要是分配多个block呢?
在重载delete操作符的函数中,是没有办法知道要本次要删除的内存
到底有多少个块,也就没有办法维护这些block中的地址表。当然,
除非把block数作为参数传进去,但这样的话,就造成了用户
的麻烦,用户在删除分配给自己的内存的时候还必须记得这块内存里
的block的数目,万一写错了,后果不堪设想。
@陈海涛
最瞧不上你这种人,动不动就说别人说的不对,总是以自己是高手自居.
你要说觉得对方写的不对,你就把你的观点写出来,指出对方哪地方写错了.
这样才能让大家受教啊.

拜托啊.....请你回到正常人的行列来.

#我只是路过这里的路人甲,看到你的回复,实在是气不过.抱歉....
如果大家对内存中的栈空间(stack)有足够的了解的话,这道题就变的容易的多了。

首先bz给的答案是对的。
原理就是利用栈空间中的一个空闲位置来存储我们的计算数据。
实际上就是把这个空闲位置当成一个临时的存储空间来用。
比如,你可以写的更简单一些。
int mystrlen(char *string)
{
*(long *)(&string - sizeof(char *) - 4) = (long)string;
while(*string++);

return ((long)string - *(long *)(&string - sizeof(char *) - 4) - 1);
}
写法虽然不同,但原理都是完全一样的。

>>>>这里跟我预期想的减两个int大小有点出入,为什么减的是0x20?
这有两个原因:
1。 栈空间永远是从高地址向低地址的方向发展的
2。 编译器至少给当前函数分配20h(32)个字节的栈空间,即使该函数里没有一个局部变量

所以,ESP(栈顶指针)会向下减去20h个字节。这样,这32个字节是给当前函数使用的,在bz的例子中,因为函数里没有一个局部变量,所以,这32个字节都是可以任意读写访问的。

只要您找到这个空闲空间的地址,你当然就可以往里面写入自己的数据喽。

只要明白上面的事情,代码就容易编写了。
&string 就是 形参string在栈空间中的地址,把这个地址减去一个sizeof(char *),这是因为,形参string下面放的是函数的返回地址(不是返回值哦),它是不能被修改的,否则就会被hack了。然后再减去4个字节,这个就是该函数的第一个空闲位置的地址了。

其实,了解栈的朋友都知道,当前正在被执行的函数永远是处于栈顶的位置,所以栈顶下面的空间都是没有人使用的,只要您不超过栈空间的范围(栈空间大小的默认值好像是8MB,不过一般的编译器都能设置这个值),你就可以访问这里面的任何一个地址。如果你像下面那么写,也没有任何问题,编译器也不会有任何抱怨,也能得到正确的值:
int mystrlen(char *string)
{
*(long *)(&string - sizeof(char *) - 400) = (long)string;
while(*string++);

return ((long)string - *(long *)(&string - sizeof(char *) - 400) - 1);
}
不过,你得注意的是减去的这个值必须是地址宽度(4个字节)的整数倍。

以上是俺的一点拙见,欢迎探讨。

另外,纠正一下楼上几位朋友的小错误。
栈空间里任何地址和内存都是静态的,所以对于当前进程来说,他们都是可读写的,不存在非法访问,所以才会出现缓冲区溢出的漏洞,会被那些hacker抓住,夺取系统的管理权限;
而堆里的内存如果在没有被分配出来的情况下,才会出现非放访问。如果您了解进程空间的布局,您就不会犯这个错误了。
re: Callback在C\C++中的实现 lymons 2010-04-21 10:27
我觉得这种做法是把简单的问题复杂化了。

在决定使用 函数指针/回调函数 来作为我们的实现方案的时候,我们要考虑的事情是:
1. 由注册函数的人来决定这个函数被调用时的参数。 还是,
2. 由这个函数的将来的调用者来决定该函数的参数。

如果是1, 则局限性比2要大,因为在注册函数的时候,并不完全清楚在函数调用时的实际情况,所以在某些场合,提前设定好了参数,到了实际调用的场合,这些参数有可能不合适。
而2,完全是由调用者根据实际情况来决定用什么样的参数来传递到调用函数中,灵活性更强。而这种场合,编码实现也最简单,无非就是一个函数指针而已。
而往往,函数的注册者和函数的调用者几乎都是一个人/用户, 所以为何非要提前来决定这个函数的调用参数,而让问题的实现变得更加复杂呢。
我认为这得不偿失。而,在商业软件开发中,很少会为了1的情况,而增加额外的测试,代码检视的成本。

可以参考linux内核的文件系统的实现。其中,VFS和实际文件系统之间的接口,就是利用了简单的函数指针,代码并不像1那么复杂。
@sun2bird
你的目的是在根节点的末尾添加一个子节点呢? 还是在根节点之后追加新节点?
我所知道的,一般情况下,xml只有一个根节点. 如果在根节点之后添入新节点的话,恐怕有问题,是吧.
re: Linux 进程互斥锁 lymons 2008-12-25 10:43
flock就是用fcntl系统调用实现的。
因为在linux内核里fcntl就是按照advise模式来实现,所以他一直是一个建议锁。

我建议你可以使用sys v的信号量,在Linux下这毕竟比posix的更常用一些。
如,使用semget(2)来创建信号量,利用semop(2)来做PV操作。
re: Linux 进程互斥锁 lymons 2008-12-24 17:32
虽然没有仔细看你写的代码,但从设计思路上也能看出一些端倪出来。

flock实现的文件锁实际上是一个建议锁。也就是意味着在这个锁很容易
收到外界的干扰。如果用于锁定的文件被某人/某进程删除了,那使用
这个文件锁的程序就很容易出现紊乱。这点也需要考虑。

从你man出来的信息来看
LinuxThreads currently does not support process-shared semaphores, thus sem_init always returns with error ENOSYS if pshared is not zero.
这说的应该指的是LinuxThread吧,指的不是进程。
sema设计的初衷就是为了让进程间进行通信,信号量如果不能共享那也就不能进行通信是吧。所以,我认为你应该放心的去用sema。至少是目前版本的大多数的linux系统都没有问题。这仅仅是俺一家之言,如有不对之处请斧正。
re: 使用 fork 所要注意的 lymons 2008-12-23 17:41
>>>>2:对于指针的拷贝,只拷贝指针的值,不拷贝指针所指向的内容(malloc 和 new 方式申请的内存)。

对于这点我不认同。
创建出来的子进程是要完全拷贝父进程的内存地址空间(内存印像),也就是子进程的内存和父进程的是一模一样的,当然也包括在堆(heap)里存放的动态内存。
所以,除了指针之外,指针指向的内容也是要拷贝出来。
虽然你打印出来的p_malloc_指向的地址都是相同的,但这个地址是存在于在两个不同的虚拟地址空间,而且内容是一模一样的,但它们是两块不同的物理内存。所以打印出相同的地址,这并不能说明父子进程的指针是指向同一块内存的。

另外,在子进程里对p_malloc_进行任意的读写都不会影响到父进程的这块同样的内存,反之,父进程来操作地址相同的内存也不会影响到子进程。

你可以在子进程的代码里,在sleep(10);之后添加对p_malloc_进行读写的语句,看它能否正确执行。请你验证一下。
bz里描述的问题是 关于list容器的size函数带来的效率的问题,而不是
怎么提高读写效率的问题,大家不要跑题啊。

而且,在读取的过程中,还要对超过固定行数之后的容器进行统计处理。

各位高手们,请仔细看bz的source的机能要求吧。
这种方法 得修改要调用函数的C++的源代码文件,在里面添加一个给C的接口函数才可以。

但是在大多数的C开发里,我们往往是看不到要调用的C++的代码,
或者说对方只给你提供C++的库的二进制代码的时候,这个时候该怎么办呢?
@沐枫
关于效率问题,俺的想法是 看这段代码放到哪里去运行,如果是在游戏,嵌入式系统等,或者成为系统性能瓶颈的时候,必须要考虑这段代码的性能的问题。
如果就是在其他的商业软件里,就不必要为这段代码考虑效率的问题,而是要着重与开发工时一起权衡考虑。我们不会为了仅仅提高了系统0.0001%的性能而浪费0.1%的工时。当然,也不是完全不考虑效率的问题,而是在一定工时里写出尽可能漂亮,尽可能高效的代码。 这仅仅是我的一点拙见,有不对之处请斧正。

另外,我对把“优化工作完全交给编译器来做”这个观点不敢苟同。
写的非常糟糕的代码,编译器的优化远远比不上用人把这段代码重写而优化的好。 完全靠编译器来优化我认为有点不妥。
@沐枫
程序里返回的并不是局部变量的指针,它返回的是传入到函数里参数的地址。
所以,调用者拿到该返回值跟实参是一个东西。
在函数返回给调用者之前,实参的内容还没有被销毁。
@沐枫
您这个最棒. 谢谢!

多谢各位热心人的指点,俺是不胜感激.

PS 如果是考虑效率的话,我是认为用C来实现可能效率比string更高一些吧.
我的个人简历第一页 我的个人简历第二页