如何使用指针从不同的函数中访问本地变量?

67

我能在不同的函数中访问局部变量吗?如果可以,应该怎么做?

void replaceNumberAndPrint(int array[3]) {
    printf("%i\n", array[1]);
    printf("%i\n", array[1]);
}

int * getArray() {
    int myArray[3] = {4, 65, 23};
    return myArray;
}

int main() {
    replaceNumberAndPrint(getArray());
}
上述代码的输出结果为:
65
4202656

我做错了什么?"4202656"是什么意思?

replaceNumberAndPrint()函数中,我是否需要复制整个数组才能多次访问它?


1
很难确定你的意图,这里提供最佳建议。但你可能会喜欢了解共享指针(shared_ptr和相关内容)。它们通过引用计数提供了垃圾回收语言的一些好的特性。但是不同,所以要谨慎使用。 - HostileFork says dont trust SE
我投票重新打开这个问题,并将其用作类似“当返回指向本地变量的指针时,我遇到崩溃,为什么?”这种性质的问题的规范重复项,而不是那个经典的规范重复项(https://dev59.com/ZWw15IYBdhLWcg3wuuCn),它更适用于“我没有崩溃,为什么不会崩溃?” - Lundin
@Lundin 这个问题有点棘手,因为它同时涉及到 C 和 C++。 - Antti Haapala -- Слава Україні
@AnttiHaapala,这个问题中与C++无关的内容并不唯一,但很遗憾,有些答案是与C++相关的,所以该标签必须保留。 - Lundin
如果在嵌入式系统上使用动态分配内存是很危险的,因此有3种可能性:使变量全局化、使其静态化或从调用程序传递一个指针到该变量。 - user1582568
10个回答

60

myArray是一个局部变量,因此指针仅在其作用域内有效(在这种情况下为包含函数getArray),一旦超出该作用域,如果您稍后访问它,则会出现未定义行为。

实际上发生的是,对printf的调用覆盖了由myArray使用的堆栈部分,然后它包含其他一些数据。

要修复您的代码,您需要在足够长的作用域中声明数组(例如您的示例中的main函数),或者将其分配给堆。如果您将其分配给堆,则需要手动释放它,或者在C++中使用RAII。

我错过的一个替代方案(可能是这里最好的选择,前提是数组不太大)是将数组包装到结构体中,从而使其成为值类型。然后返回它会创建一个在函数返回后仍然存在的副本。有关此信息,请参见tp1答案


2
或者你可以声明它为静态的。 - user1481860
3
如果数组的内容不是恒定不变的话,在多线程应用程序中将其设置为静态的语义会有很大的区别。 - CodesInChaos
3
可以,但这是解决问题的方法,应该指出来,对吧? - user1481860
2
指出问题当然是有用的,但你也需要指出缺点,所以你的第一条评论有点不完整。 - CodesInChaos
5
我认为把 "static" 推荐作为解决这个问题的方法是有害的,从来没有什么帮助。对于新手来说,这变成了一个公式化的权宜之计,取代了真正理解问题和编写正确代码的过程。当其他人继承新手的代码时,他们将会对到处都是毫无意义的静态变量感到震惊。 - R.. GitHub STOP HELPING ICE
显示剩余3条评论

19

一旦局部变量超出其作用域,就无法访问它。这就是局部变量的含义。

当您在replaceNumberAndPrint函数中访问数组时,结果为未定义行为。第一次似乎可以正常工作只是一个幸运的巧合。可能您指向的内存位置在堆栈上未分配并且在第一次调用时仍然正确设置,但是printf的调用在其操作期间通过将值推送到堆栈上覆盖了它,这就是为什么第二次对printf的调用显示不同内容。

您需要将数组数据存储在堆上并传递指针,或者将其存储在保持作用域的变量中(例如全局变量或限定在主函数内的变量)。


8
尝试像这样做。如果局部定义,你的操作会“破坏”myArray
#include <stdio.h>
#include <stdlib.h>

void replaceNumberAndPrint(int * array) {
 printf("%i\n", array[0]);
 printf("%i\n", array[1]);
 printf("%i\n" , array[2]);
 free(array);
}

int * getArray() {
 int * myArray = malloc(sizeof(int) * 3);
 myArray[0] = 4;
 myArray[1] = 64;
 myArray[2] = 23;
 //{4, 65, 23};
 return myArray;
}

int main() {
 replaceNumberAndPrint(getArray());
}

更多: http://www.cplusplus.com/reference/clibrary/cstdlib/malloc/

编辑:正如评论正确指出的那样: 更好的方法是:

#include <stdio.h>
#include <stdlib.h>

void replaceNumberAndPrint(int * array) {
    if(!array)
        return;

    printf("%i\n", array[0]);
    printf("%i\n", array[1]);
    printf("%i\n" , array[2]);
}

int * createArray() {
    int * myArray = malloc(sizeof(int) * 3);

    if(!myArray)
        return 0;

    myArray[0] = 4;
    myArray[1] = 64;
    myArray[2] = 23;
    return myArray;
}

int main() {
    int * array = createArray();
    if(array)
    {
        replaceNumberAndPrint(array);
        free(array);
    }
    return 0;
}

1
如果您在代码中使用此功能,请务必非常好地注释它。通过一个函数分配内存,再通过另一个函数释放内存是非常危险的,除非有适当的文档记录,即使有文档记录,也容易出现内存泄漏!最好在main函数中分配数组,并在不再需要时释放它。 - Shaihi
@Shaihi,这是真的,而且这段代码非常天真,因为它没有检查malloc()是否成功分配了内存。但我认为OP会理解整个意思。 - user418748
2
虽然这个代码可以运行,但是它看起来很丑。你应该把函数名(getArray => createArray)改成更能描述它们行为的名称。而且让replaceNumberAndPrint删除源数组似乎不是一个好主意。我更倾向于将删除和打印分成两个不同的函数。 - CodesInChaos
2
@Muggen:你不喜欢用array[i]代替*(array+i)吗? - jweyrich
@jwerich,嗯,好的,我会修复。这样听起来更好。它可能会引发更多问题而不是答案。 - user418748
显示剩余2条评论

2

当函数返回时,局部变量会失去作用域,因此您不能返回指向局部变量的指针。

您需要在堆上动态分配它,使用mallocnew。例如:

int *create_array(void) {
    int *array = malloc(3 * sizeof(int));
    assert(array != NULL);
    array[0] = 4;
    array[1] = 65;
    array[2] = 23;
    return array;
 }
 void destroy_array(int *array) {
     free(array);
 }
 int main(int argc, char **argv) {
     int *array = create_array();
     for (size_t i = 0; i < 3; ++i)
         printf("%d\n", array[i]);
     destroy_array(array);
     return 0;
 }

或者,您可以将数组声明为静态的,但需要注意语义上的区别。例如:

int *get_array(void) {
    static int array[] = { 4, 65, 23 };
    return array;
 }
 int main(int argc, char **argv) {
     int *array = get_array();
     for (size_t i = 0; i < 3; ++i)
         printf("%d\n", array[i]);
     return 0;
 }

如果你不知道 static 是什么意思,请阅读 这个问题和答案


2

正确的做法如下:

struct Arr {
   int array[3];
};
Arr get_array() {
   Arr a;
   a.array[0] = 4;
   a.array[1] = 65;
   a.array[2] = 23;
   return a;
}
int main(int argc, char **argv) {
   Arr a = get_array();
   for(size_t i=0; i<3; i++)
       printf("%d\n", a.array[i]);
   return 0;
}

为了理解为什么需要这样做,您需要了解sizeof(array)如何工作。 C语言(因此c ++)努力避免复制数组,您需要使结构体超过该范围。需要复制的原因是由于作用域 - get_array()函数的作用域消失,需要从该作用域中仍然需要的每个值都需要被复制到调用作用域。

1
我知道这是相当久以前的事情了,但你不是需要使用typedef或者像struct Arr a这样分配结构体吗? - sherrellbc
在C语言中需要使用typedef来定义结构体类型,例如typedef struct {} Foo;,而在C++中使用struct Foo{...}语法即可自动定义struct FooFoo两种类型,无需使用typedef。@sherrellbc - melpomene

2

当你离开getArray函数时,myArray就会超出作用域。你需要在堆上为它分配空间。


2

您的代码调用了未定义行为,因为myArraygetArray()返回后就超出了作用域,任何试图使用(解引用)悬空指针的尝试都是未定义行为。


1

C++解决方案:

“我能在不同函数中访问局部变量吗?如果可以,如何做?”

答案是否定的,在函数结束后就无法访问局部变量了。这时候局部变量已经被销毁。

C++ 中处理返回数组的方法是使用容器,例如 std::array(固定大小)或 std::vector(动态大小)。

例如:

void replaceNumberAndPrint(const std::array<int, 3>& array) {
    printf("%i\n", array[0]);
    printf("%i\n", array[1]);
    printf("%i\n", array[2]);
}

std::array<int, 3> getArray() {
    std::array<int, 3> myArray = {4, 65, 23};
    return myArray;
}

在第二个函数中,返回的值由编译器进行了优化,因此您不需要付出实际复制数组的代价。

0
在这段代码中,您使用了指向本地对象的指针,但是当函数返回时,所有本地变量都会超出范围。如果您将分配内存(使用malloc()函数进行分配),则不会丢失或覆盖任何数据。
int* getArray(int size) {
    int *myArray = (int*)malloc(size*sizeof(int));
    myArray[0] = 4;
    myArray[1] = 65;
    myArray[2] = 23;
    return myArray;
}

int main() {
    int i;
    int *vector = getArray(3);
    for(i=0;i<3;i++)
    {
        printf("%i\n",vector[i]);
    }
    getch();
    return 0;
}

这段代码将打印出所有的数组元素,不会发生覆盖。

0

在你的 .c 文件中,静态或全局变量都可以解决问题 ;)

然而,整个程序运行期间将占用这 3 个字节,但是对于像这样简单的东西,避免使用 malloc(建议用于大型数组)。

另一方面,如果外部函数修改指针,则内部的 'myArray' 将被修改,因为它指向它,就是这样。

int myArray[3];
int * getArray() {
    myArray[0] = 4;
    myArray[1] = 65;
    myArray[2] = 23;
    return myArray;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接