Daily Archives: February 3, 2007

char*和char[]

一直就认为C中的char*和char几乎是一样的,要是说区别,那就是char[]在声明之后,地址值是只读的,而char*的值是可变的。

不过今天看到两个程序:

A:

char* get_str(void)
{
char str[] = {“abcdefghijk”};
return str;
}
int main(int argc, char* argv[])
{
char* p = get_str();
printf(“%sn”,p);
return 0;
}

B:

char* get_str(void)
{
char *str = {“abcdefghijk”};
return str;
}
int main(int argc, char* argv[])
{
char* p = get_str();
printf(“%sn”,p);
return 0;
}

理论上,两个程序好像都不可以,因为都是返回一个局部变量。可结果是,第一个有warning,运行的确什么都没有打印出来;而第二个一切正常,能打印出字母来。这是为什么呢?我的gcc版本是 version 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)

将c代码转化成汇编:

gcc-local-varibles.png
我们可以看出,两个程序只有在get_str 函数有区别,前者是将.LC0,即字符串重新复制了一遍,进行修改;而后者直接将字符串的地址赋给了局部变量。movl -4(%ebp), %eax 则是将这个地址返回给main。

看来gcc编译器不是像我们想象的那样,在函数中生成一个字符串,在进行操作;而是有一个全局的字符串,如果是char[],则会生成一个副本,如果是char*,则只是简单的生成一个引用。

可是问题没有结束,关键在于这个字符串是在 .section .rodata下面的,也就是只读的,有意思的是如果我们将B中的get_str改成:

char* get_str(void)
{
char *str = {“abcdefghijk”};
str[1]=’p’;
return str;
}

之后,运行会出现 Segmentation fault,而进行相同修改的A代码可以运行,但是打印出来的是无意义的字符串。

这个问题解决了,还有一些很有意思的东西。gcc 现在的版本为了加快运行速度,都将变量按照16字节边界存储。例如下面的代码:

void doit(void)
{
}
int main(int argc, char* argv[])
{
char a[10];
doit();
return 0;
}

生成的汇编代码main部分为:

main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $16, %esp
call doit
addl $16, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
我们可以发现main函数在初始化的时候 预留了16个字节的空间给局部变量。如果我们在main中再添加一个char b,结果还是16个字节,因为gcc会将所有变量总共需要的空间算出来,再转化为16的倍数。是不是所有的情况都是这样呢?看看下面的代码:

void doit(int n)
{
}
int main(int argc, char* argv[])
{
char a[120];
doit(9);
return 0;
}
这个时候相关汇编代码变成了:

subl $132, %esp
movl $9, (%esp)
call doit
movl $0, %eax
addl $132, %esp
为什么是132而不是128呢?因为main函数调用get_str的时候需要预先填入其所需的参数,然后才执行call填入IP,所以gcc就偷懒一起留了,因为 参数是一个int,为4个字节,所以总共需要留出128+4=132个字节。

具体的内存分布图参见下图

试用mono(二)新建GUI项目篇

上次介绍了如何导入一个现有的VS .Net项目并运行,这次我们来看看mono如何创建一个新的项目。

首先在mono的welcome界面中点击Start a New Project,这时会出现下面的界面。其中,Console Project是新建一个C#的控制台项目;Empty Project是创建一个空的项目,没有任何文件;Library是新建一个库项目。在C#的子项ASP.NET中,还有一个Web Application,这个可以新建一个ASP.Net 的项目。以上四项与VS .Net中都是对应的。不过,在这里大家会发现会有一个Gtk# 2.0,这个是什么东东呢?到官方完站上看了一下,这个部分下次再介绍。
New Solution

选择新建一个Cosnole Project,系统自动创建一个项目。项目中除了用于设定生成的有关程序集的常规信息参数必需的AssemblyInfo.cs文件之外,还有一个文件Main.cs,这是项目的主体。mono自动生成了一个Hello World的示例:

右击Solution工具栏中的项目,出现Add菜单,里面有New Dialog,New Window,New Widget和New Action Group。这点很有意思,在VS .Net中,Console项目是不能创建GUI的,至于那些Drawing命名空间则完全屏蔽了。而在mono中可以自由地创建GUI控件。本来想再写一个GUI篇,看来没有必要了,写在一起就好了。

我们选择新建一个窗口,此时会出现一个窗口如下:

New Window

这里有一个GTK# Window,还有一个Window。其实本质上它俩都是基于GTK#的window控件。GTK# Window是基于GTK#的控件,GTK#是gtk+的.Net绑定集,可以用它来编写生成gtk的原生程序。GTK#的文档可以从官方得到,而window则是gtk+中的控件,它与Visual C# .Net的window不是一个东西了。

如果新建一个GTK# Window,那么自动生成的代码是:

using System;
using Gtk;
using GtkSharp;

namespace con1
{
public class Form2 : Window
{
public Form2 () : base (“MyWindow”)
{
}
}
}

如果新建一个window,系统生成的代码是:

using System;

namespace con1
{

public class Form1 : Gtk.Window
{

public Form1() :
base(“”)
{
Stetic.Gui.Build(this, typeof(con1.Form1));
}
}
}

其实,从语义上来说,两者都是一样的。我们注意到第二个窗口的代码中有Stetic.Gui.Build方法,那么Stetic是什么呢?Stetic 的官方解释是:Stetic is the new GUI designer for creating Gtk# applications.我们可以把它理解成一个GUI的设计器,正因为如此,我们可以发现,Form1的界面有Source code和Designer两部分

GUI Design

在GUI的设计方面,包括控件和定位,我感觉就是gtk的风格。例如控件不是绝对定位,有x、y坐标和长宽,而是类似flow一个挨一个的。因为gtk的定位一直觉得很让人郁闷,所以这部分就不做了。至于控件的类库,倒是和C#.Net的很类似,这里就不说了。

最后是编译执行,会在bin/Debug或bin/Release下面生成一个exe文件,直接运行就可以了。下图是运行结果。

MyWindow.png