[实践OK]c语言指向指针的指针源代码分析, C语言指针传递和内存分配。

jackxiang 2008-4-14 12:29 | |
背景:指针传入函数和变量传入函数也一样,变量有变量本身,及变量的地址,而指针也一样,有指针本身,和指针的地址,下面根据代码来实践一下这个问题。
C语言指针传递详解:http://www.cnblogs.com/archimedes/p/c-transfer-point.html
从函数返回对象经常使用以下两种技术:
使用malloc在函数内部分配内存并返回其地址,调用者负责释放返回的内存
传递一个对象给函数,让函数修改它,这样分配和释放对象的内存都是调用者的责任

先讲难度大一点的,指针的地址传入函数里,传递指针的指针-将指针传递给函数的时候,传递的是值,如果希望修改原指针而不是指针的副本,就需要传递指情况一,针的指针,如下,allocateArray.c :

gcc -g -o allocateArray allocateArray.c

[root@test pointerparam]# ./allocateArray
45
45
45
45
45
用gdb看一下其指针地址,为何这样就能实现在函数里malloc后,在外面依然能够获取到malloc里的值呢?是因为传入的指针的地址,相当于这个malloc的内存根本没有退出函数后销毁,而且传入的地址是指针的地址,不是指针,这两点保证了其地址是同一个地址,不是副本,所以,函数退出后还在,需要外面作free(;vector),vector=NULL:
(gdb) b 4
Breakpoint 1 at 0x40053a: file allocateArray.c, line 4.
(gdb) b 18
Breakpoint 2 at 0x4005ad: file allocateArray.c, line 18.
(gdb) r
Breakpoint 2, main () at allocateArray.c:18
18          allocateArray(&vector,5,45);
(gdb) p &vector
$1 = (int **) 0x7fff3cb989f0         //函数外的地址: 0x7fff3cb989f0
(gdb) c
Continuing.

Breakpoint 1, allocateArray (arr=0x7fff3cb989f0, size=5, value=45) at allocateArray.c:5
5           *arr = (int*)malloc(size*sizeof(int));
(gdb) p arr
$2 = (int **) 0x7fff3cb989f0    //这个地址在函数里也一直是传入的地址: 0x7fff3cb989f0
(gdb) n
6           if(*arr != NULL){
(gdb) p arr
$4 = (int **) 0x7fff3cb989f0
(gdb) n
7               if(*arr != NULL){
(gdb) n
8                   for(i=0;i<size;i++){
(gdb) n
9                       *(*arr+i) = value;
(gdb) n
main () at allocateArray.c:19
............next............
19          for(i = 0; i < 5; i++) {
(gdb) n
20              printf("%d\n", vector[i]);
(gdb) p &vector
$6 = (int **) 0x7fff3cb989f0     //发现没到函数里转一圈后,这个地址一直是: 0x7fff3cb989f0
(gdb) n
45
19          for(i = 0; i < 5; i++) {//打印出函数里赋的值是没有问题的..正常运行结束。


情况二:下面这个版本的allocateArray函数传递了一个数组指针、数组的长度和用来初始化数组元素的值,返回指针只是为了方便

[root@test pointerparam]# gcc  allocateArray3.c
[root@test pointerparam]# ./a.out
45
45
45
45
45
这个值是变了,但指针是没有变的,也就是说指针做了它该做的工作,并没有像第一种那样:  allocateArray(&vector,5,45);实现对指针本身地址通过函数进行了修改,这儿只是对指针里的值做了修改,是不一样的。
用gdb来跟踪一下这个指针的传指针值的操作:
[root@test pointerparam]# gdb allocateArray3
(gdb) b 15
Breakpoint 1 at 0x400571: file allocateArray3.c, line 15.
(gdb) b 5
Breakpoint 2 at 0x400536: file allocateArray3.c, line 5.
(gdb) r
Starting program: /tmp/pointerparam/allocateArray3
Breakpoint 1, main () at allocateArray3.c:15
15          int* vector = (int*)malloc(5 * sizeof(int));
(gdb) p vector
$1 = (int *) 0x0   //此时是光一个指针还没有分配指针指和的malloc真实数据地址
(gdb) n
16          allocateArray(vector, 5, 45);
(gdb) p vector
$2 = (int *) 0xb447010 //分配地址:0xb447010 这个值会带入进去,并在里面对其作赋值操作,出函数后这个值还在,因为在传入前就已经分配好空间,在函数里只是赋值,把东西放进这个空间,函数返回后空间并没有销毁,因为是在函数外。
(gdb) c
Continuing.
Breakpoint 2, allocateArray (arr=0xb447010, size=5, value=45) at allocateArray3.c:6
6           if(arr != NULL) {
(gdb) c
Continuing.
45
45
45
45
45


情况三,指针值传入函数里面,这样会出现一个segment default的,情况三作一下修改:
allocateArray2.c

gcc -g -o allocateArray2 allocateArray2.c
[root@test pointerparam]# ./allocateArray2
Segmentation fault


如这样写呢?     *arr = (int*)malloc(size*sizeof(int));

如果想这样,编译都会报错,如下:
porinter.c:6:10: 警告:赋值时将指针赋给整数,未作类型转换 [默认启用]

因为文件名传入的是一个指针,而*arr是一个值,不再是一个指针了。
而传入的是指针的指针就不一样了,如前面的例子1:void allocateArray(int ** arr,int size,int value){
那么因为函数int ** arr是指针的指针,也就是这个指针是可以修改的,这个*arr它不是一个值了,而是一个指针,所以,它可以给它malloc一个空间,并把这个空间给到*arr这个脂针地址里去,这块一定要区分开来,否则指向指针这一块的概念就没彻底理解清楚。
再理解前面引用的Url里的这一段话,加强深入理解传入指向指针的指针是可以修改指针的地址,指针也理一个数据,和int char是一样的东西,它也是能被修改的,而一维指针可以修改int char,int这样的值,但指针地址不变,而指针的指针它的地址都能被修改,这就是指针的精华人所在,摘录如下:
传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问,要在某个函数中修改数据,需要用指针传递数据,当数据是需要修改的指针的时候,就要传递指针的指针,传递参数(包括指针)的时候,传递的是它们的值,也就是说,传递给函数的是参数值的一个副本,也就是说这儿数据是需要修改的指针的时候,就要传递指针的指针,而咱们只传了指针,修改后退出函数这个指针并没有变,销毁函数后,没有对指针作出修改,等于空做了一次运算,像做梦一样,函数并没改变什么,所以出现错误。

为何会出现segment default的情况?是因为指针传入函数后进行了malloc,而这个指针是在函数里经过malloc时给修改了指向地址,而这个malloc的空间因为函数退出而回收了指针传值地址,也就是其并没有记得下来,其malloc出来的存放整数的空间还在,但退出函数时就丢了,为此,在外面打印一个不存在的地址,当然会出现了segment default的情况,gdb分析如下:
(gdb) b 18
Breakpoint 1 at 0x40059a: file allocateArray2.c, line 18.
(gdb) b 19
Breakpoint 2 at 0x4005ad: file allocateArray2.c, line 19.
(gdb) b 4
Breakpoint 3 at 0x40053a: file allocateArray2.c, line 4.
(gdb) r
Starting program: /tmp/pointerparam/allocateArray2

Breakpoint 1, main () at allocateArray2.c:18
18          allocateArray(vector,5,45);
(gdb) n

Breakpoint 3, allocateArray (arr=0x0, size=5, value=45) at allocateArray2.c:5 //arr传入是0x0,也就是副本vector的地址。
5           arr = (int*)malloc(size*sizeof(int));
(gdb) n
6           if(arr != NULL){
(gdb) p arr
$1 = (int *) 0x14c3c010 //经malloc后是0x14c3c010
(gdb) c
Continuing.

Breakpoint 2, main () at allocateArray2.c:19
19          for(i = 0; i < 5; i++) {
(gdb) p vector //返回main函数后,这个vector还是0x0,并没有经过函数而改变,当然,下面对其进行打印会Segmentation fault.
$2 = (int *) 0x0
(gdb) n
20              printf("%d\n", vector[i]);
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0x00000000004005c3 in main () at allocateArray2.c:20
20              printf("%d\n", vector[i]);

回顾总结:
allocateArray.c                    allocateArray2.c                  
#include <stdio.h>                   #include <stdio.h>                                          
#include <stdlib.h>                   #include <stdlib.h>                                          
void allocateArray(int *arr,int size,int value){           void allocateArray(int ** arr,int size,int value){      
    int i;                           int i;                                                            
    arr = (int*)malloc(size*sizeof(int));                 *arr = (int*)malloc(size*sizeof(int));                
    if(arr != NULL){                       if(*arr != NULL){                                          
        if(arr != NULL){                     if(*arr != NULL){                                      
            for(i=0;i<size;i++){                       for(i=0;i<size;i++){                              
                arr[i] = value;                     *(*arr+i) = value;                              
            }                             }                                                          
        }                         }                                                              
    }                             }                                                                  
}                         }                                                                      
                                                                                                      
int main(){                       int main(){                                                        
    int i;                           int i;                                                            
    int *vector = NULL;                       int *vector = NULL;                                      
    allocateArray(vector,5,45);                   allocateArray(&vector,5,45);                          
    for(i = 0; i < 5; i++) {                     for(i = 0; i < 5; i++) {                                    
        printf("%d\n", vector[i]);                 printf("%d\n", vector[i]);                            
    }                             }                                                                  
    free(vector);                         free(vector);                                                  
}                         }                                                                      
结论是malloc在函数里都得销毁掉,详细如下所述:
C语言是传值调用的,左边的你传递进去vector的地址,右边的是你传递了vector这个指针的地址值,所以左边的allocateArray函数内对arr的修改不会对函数外vector的值有什么影响,但是右边就不一样了,对arr的修改就是对vector变量所在的空间中的值的修改,也就是修改了vector的值,所以左边的allocateArray函数内对arr的修改不会对函数外vector的值有什么影响,那这个左边的malloc有没有运行完后还在呢? 右边这个如果不free会有内存泄漏的呀。
两边的都要销毁,只是左边的你在函数外没办法销毁。
【活跃】回忆未来-向东-Jàck 15/3/18 星期三 11:13:40
哈哈,了解了,多谢兄弟啊,相当感谢,我都说这malloc这玩意用时要注意才是。搞不好就泄漏了呢。
11:13:53
【潜水】Now&Fight 15/3/18 星期三 11:13:53
嗯嗯


————————————————————————————————————————————————————————————————
内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。(在函数中不要返回栈内存,但可以返回动态分配的内存)。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
例子:
(1)
voidGetMemory(char *p)
{
     p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}
请问运行Test 函数会有什么样的结果?
答:程序崩溃(段错误)。因为GetMemory 并不能传递动态内存,Test 函数中的str 一直都是 NULL。strcpy(str, "helloworld");将使程序崩溃。
(2)
char *GetMemory(void)
{
    char p = "hello world";
    return p;
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);
}
请问运行Test 函数会有什么样的结果?
答:可能是乱码。
因为GetMemory 返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。
(3)
void GetMemory2(char **p, int num)
{
    *p = (char *)malloc(num);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf(str);
}
请问运行Test 函数会有什么样的结果?
答:
(1)能够输出hello
(2)内存泄漏。
(4)
void Test(void)
{
    char *str = (char *) malloc(100);
    strcpy(str, “hello”);
    free(str);//没有将str置为NULL,
if(str != NULL)
{
    strcpy(str, “world”);
    printf(str);
}
}
请问运行Test 函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str 成为野指针,
if(str != NULL)语句不起作用。
**********************************************************************************************
void GetMemory2(char **p, int num)
{
    *p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
    char *str = NULL;
    GetMemory2(&str, 100); // 注意参数是 &str,而不是str
    strcpy(str, "hello");
    cout<< str << endl;
    free(str);
}
如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”.由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。
char *GetMemory3(int num)
{
     char *p = (char *)malloc(sizeof(char) * num);
     return p;
}
void Test3(void)
{
     char *str = NULL;
     str = GetMemory3(100);
     strcpy(str, "hello");
     cout<< str << endl;
     free(str);
}
**********************************************************************************************
用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return 语句用错了。这里强调不要用return 语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡
char*GetString(void)
{
     char p[] = "hello world";
     return p; // 编译器将提出警告(告诉你返回的是局部变量)
}
void Test4(void)
{
     char *str = NULL;
     str = GetString(); // str 的内容是垃圾
     cout<< str << endl;
}
用调试器逐步跟踪Test4,发现执行str= GetString 语句后str 不再是NULL 指针,但是str的内容不是“hello world”而是垃圾。
char *GetString2(void)
{
     char *p = "hello world";
     return p;
}
void Test5(void)
{
     char *str = NULL;
     str = GetString2();
     cout<< str << endl;
}
函数Test5 运行虽然不会出错,但是函数GetString2 的设计概念却是错误的。因为GetString2 内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。

告诉你返回的是一个局部变量,警告。

来自:http://blog.csdn.net/ios_long/article/details/7019718
————————————————————————————————————————————————————————————————
#include <stdio.h>
int main()
{
   char **pa;
   char *p[3];
   pa = p;
   int n,m;
   *(pa+0) = "BOOK";
   *(pa+1) = "YOU";
   *(pa+2) = "LINUX";
   for(n = 0;n<3;n++)
   {
    printf("\npa+%d= %04x\n",n,pa+n);
    printf("Addr of * (pa+%d) = %04x\n",n,*(pa+n) );
    printf("string pointed by *(pa+%d) = %s\n",n,*(pa+n) );
    for(m=0;*(*(pa+n)+m)!='\0';m++)
    {
        printf("* (*(pa+%d)+%d) = %c\t",n,m,*(*(pa+n)+m));
    }
    printf("\n");
   }
}
运行结果:
pa+0= bfbfeb80
Addr of * (pa+0) = 8048668
string pointed by *(pa+0) = BOOK
* (*(pa+0)+0) = B       * (*(pa+0)+1) = O       * (*(pa+0)+2) = O       * (*(pa+0)+3) = K

pa+1= bfbfeb84
Addr of * (pa+1) = 804866d
string pointed by *(pa+1) = YOU
* (*(pa+1)+0) = Y       * (*(pa+1)+1) = O       * (*(pa+1)+2) = U

pa+2= bfbfeb88
Addr of * (pa+2) = 8048671
string pointed by *(pa+2) = LINUX
* (*(pa+2)+0) = L       * (*(pa+2)+1) = I       * (*(pa+2)+2) = N       * (*(pa+2)+3) = U       * (*(pa+2)+4) = X

注意:
char **pa是指向char*性的指针,而char *p[3] 其本质上是一个数组,而分别为char *p[0] char *p[1] char *p[2] 而,通过pa=p后,pa指向char *p[3] 的首地址及*p[0] (数组名称即是其首地址)。为此可以pa+1,pa+2等的操作,分别指向下面book的地址和You的地址,也就是*(pa+0)  = "BOOK" 就是让系统分配一个内存地址将BOOK放入,然后pa+0就指向BOOK的地址,以此类推,这个分配多少和操作系统有关,不像Java有回收机制,这个基本上由操作系统来分配!

作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/1007/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!


最后编辑: jackxiang 编辑于2016-1-31 21:21
评论列表
发表评论

昵称

网址

电邮

打开HTML 打开UBB 打开表情 隐藏 记住我 [登入] [注册]