关于char*与char[]

char *char []总有说不尽的故事,有很多时候,我们都不需要去仔细区分它俩,但是它俩又确实有很大的不同。最近看到一些大企业的面试题里又喜欢折腾这方面的内容,我便顺势来作个对比。

主要还是通过示例代码来说明它俩的不同。

示例一:

/* test1.c */
#include <stdio.h>
int main()
{
    char a[] = "abcdef";
    char *b = "abcdef";
    printf("%d",sizeof(a));
    printf("%d",sizeof(b));
    return 0;
}

这个区别比较明显,char a[]表示数组,所以sizeof(a)会得到数组占用空间的大小,这里为7,别忘记了最后那个'\0'哟。而char *b是一个字符指针,32系统里指针大小为4字节,所以sizeof(b)的果果是4。

示例二:

/* test2.c */
#include <stdio.h>
void print_size(char a[])
{
    printf("%d",sizeof(a));
}
int main()
{
    char a[] = "abcdef";
    print_size(a);
    printf("%d",sizeof(a));
    return 0;
}

main()函数里的sizeof(a)为7,在示例一中已经讲过了。而print_size(a)函数中的sizeof(a)会是多少呢,答案是4,你会觉得奇怪吗?其实print_size(char a[])print_size(char *a)是一样的,它并不会为你记录数组的大小,所以常常,我需要传入一个size_t类型的参数来告诉函数这个串有多长。

示例三:

/* test3.c */
#include <stdio.h>
void ps(char a[11])
{
    int i;
    for (i = 0; i < 10; ++i)
        a[i] = '0' + i;
    a[10] = '\0';
    printf("%d\n", sizeof(a));
    printf("%s\n", a);
}
int main(void)
{
    char a[] = "1234";
    ps(a); printf("%d\n", sizeof(a));
    printf("%s\n", a);
    return 0;
}

这是一个很有趣的例子,输出果果为:

4
0123456789
5
0123456789

第一个4告诉你,char a[]变成char a[11]并不能阻止a成为一个char *型指针的命运。而printf()函数是一个相当愚蠢的函数,它负责不停地打印出字符只到遇到'\0'为止,你可以试试将一个字符串的最后那个'\0'改成任意一个可打印字符,然后使用printf()函数输出,看看效果如何。最后的那句printf("%s\n",a);再次表现了printf()函数的愚蠢性。你奇怪为什么printf("%d\n",sizeof(a));还能正确输出果果5呢,这至少可见它并不是通过查看'\0'来做简单判断的。

示例四:

/* test4.c */
#include <stdio.h>
char* f1()
{
    char a[]="Hello, world![]";
    return a;
}
char* f2()
{
    char *a="Hello, world!*";
    return a;
}
int main()
{
    char *s1;
    char *s2;
    s1=f1();
    s2=f2();
    puts(s1);
    puts(s2);
    return 0;
}

使用命令gcc -S test4.c可以得到编译器转换成的汇编代码test4.s,如下:

/* test4.s */
    .file   "question.c"
    .text
.globl f1
    .type   f1, @function
f1:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    %gs:20, %eax
    movl    %eax, -12(%ebp)
    xorl    %eax, %eax
    movl    $1819043144, -28(%ebp)
    movl    $1998597231, -24(%ebp)
    movl    $1684828783, -20(%ebp)
    movl    $6118177, -16(%ebp)
    leal    -28(%ebp), %eax
    movl    -12(%ebp), %edx
    xorl    %gs:20, %edx
    je  .L3
    call    __stack_chk_fail
.L3:
    leave
    ret
    .size   f1, .-f1
    .section    .rodata
.LC0:
    .string "Hello, world!*"
    .text
.globl f2
    .type   f2, @function
f2:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    $.LC0, -4(%ebp)
    movl    -4(%ebp), %eax
    leave
    ret
    .size   f2, .-f2
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    call    f1
    movl    %eax, 28(%esp)
    call    f2
    movl    %eax, 24(%esp)
    movl    28(%esp), %eax
    movl    %eax, (%esp)
    call    puts
    movl    24(%esp), %eax
    movl    %eax, (%esp)
    call    puts
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.1-4ubuntu9) 4.4.1"
    .section    .note.GNU-stack,"",@progbits

要真正区分char []char *,查看汇编代码才是王道,以上是通过gcc -S得到的AT&T汇编代码,以.globl开头表示全局,f1,f2,main前面都有.globl表示它们三个是全局函数。

你会发现f1()函数的"Hello, world![]"是通过movl指令,将一个个ascii码顺序排放而得到的,而f2()函数是将"Hello, world!*"放到了常量数据区,然后通过地址值进行索引。一个在指令段里,一个在数据段里,两者当然是截然的不同了,这是它们的根本区别。常量数据段表明了它有不可改变的属性,若加入s2[0]='A';会产生段错误(Segmentation fault),这便验证了此种说法。而在指令区的char []则要自由得多。

发表于 2010年05月03日 10:34   评论:0   阅读:2039  



回到顶部

首页 | 关于我 | 关于本站 | 站内留言 | rss
python logo   django logo   tornado logo