怎样将函数内部的字符串数组通过函数参数传递出来?

seai 2017-05-21 11:53:39
下面做法,编译运行结果是正常的,但,直接把内部变量的地址传递出去,在外面访问是不是不是安全?
我理解为,函数内部地址空间的内容,出了函数后就不保证不被人修改了。这样虽然能访问到,但值很可能不是期望的?


#include <stdio.h>
int foo(const char *** s)
{
const char* s1[]={"abc","efg"};
*s=&s1;
return 0;
}

int main(int argc, char ** argv)
{
const char **s;
foo(&s);
printf(" %s %s ", s[0], s[1]); //输出结果: abc efg
}
...全文
1031 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
ZacCchary 2017-06-19
  • 打赏
  • 举报
回复
引用 18 楼 seai 的回复:
这个解比较靠谱。 补充问一句:如果里面不是字符串字面值,而是字符串变量,那么,其中每一个元素都要重新分配堆空间吧。 [quote=引用 17 楼 fjzqabx 的回复:]

#if 1

void get_ptr(char ***save)
{
	//argv数组中的每个元素都是指向rodata段的指针,rodata段中数据的生命周期等同程序生命周期
	char *argv[] = {"hello", "C", "world"};

	*save = argv;//argv本身就是字符指针数组中元素的首地址,直接赋给二级字符指针即可
}

int main(void)
{
	char **save = NULL;
	
	get_ptr(&save);
	
	//完全不必担心save数组中的元素出了get_ptr函数被释放,除非你的程序走到头了.
	printf("%s\t%s\t%s\n", save[0], save[1], save[2]);

	return 0;
}

#endif
[/quote]
引用 18 楼 seai 的回复:
这个解比较靠谱。 补充问一句:如果里面不是字符串字面值,而是字符串变量,那么,其中每一个元素都要重新分配堆空间吧。 [quote=引用 17 楼 fjzqabx 的回复:]

#if 1

void get_ptr(char ***save)
{
	//argv数组中的每个元素都是指向rodata段的指针,rodata段中数据的生命周期等同程序生命周期
	char *argv[] = {"hello", "C", "world"};

	*save = argv;//argv本身就是字符指针数组中元素的首地址,直接赋给二级字符指针即可
}

int main(void)
{
	char **save = NULL;
	
	get_ptr(&save);
	
	//完全不必担心save数组中的元素出了get_ptr函数被释放,除非你的程序走到头了.
	printf("%s\t%s\t%s\n", save[0], save[1], save[2]);

	return 0;
}

#endif
[/quote]对的,因为字符指针数组的每个元素都只是一个4字节大小的指针,要给每个指针指定一个字符串(不论常量还是变量)的指向,按照你的意思字符串需要全局段的区域只有字符串常量区和堆区动态分配出来的内存.
seai 2017-06-15
  • 打赏
  • 举报
回复
这个解比较靠谱。 补充问一句:如果里面不是字符串字面值,而是字符串变量,那么,其中每一个元素都要重新分配堆空间吧。
引用 17 楼 fjzqabx 的回复:

#if 1

void get_ptr(char ***save)
{
	//argv数组中的每个元素都是指向rodata段的指针,rodata段中数据的生命周期等同程序生命周期
	char *argv[] = {"hello", "C", "world"};

	*save = argv;//argv本身就是字符指针数组中元素的首地址,直接赋给二级字符指针即可
}

int main(void)
{
	char **save = NULL;
	
	get_ptr(&save);
	
	//完全不必担心save数组中的元素出了get_ptr函数被释放,除非你的程序走到头了.
	printf("%s\t%s\t%s\n", save[0], save[1], save[2]);

	return 0;
}

#endif
ZacCchary 2017-05-23
  • 打赏
  • 举报
回复

#if 1

void get_ptr(char ***save)
{
	//argv数组中的每个元素都是指向rodata段的指针,rodata段中数据的生命周期等同程序生命周期
	char *argv[] = {"hello", "C", "world"};

	*save = argv;//argv本身就是字符指针数组中元素的首地址,直接赋给二级字符指针即可
}

int main(void)
{
	char **save = NULL;
	
	get_ptr(&save);
	
	//完全不必担心save数组中的元素出了get_ptr函数被释放,除非你的程序走到头了.
	printf("%s\t%s\t%s\n", save[0], save[1], save[2]);

	return 0;
}

#endif
wallesyoyo 2017-05-23
  • 打赏
  • 举报
回复
引用 8 楼 seai 的回复:
[quote=引用 7 楼 cfjtaishan 的回复:] [quote=引用 6 楼 seai 的回复:] [quote=引用 5 楼 cfjtaishan 的回复:] foo的操作获得的s1的地址是没有意义的,得到的结果也是未定义的。因为s1是在foo函数栈上申请的局部变量指针数组,foo调用结束后,s1指针数组就会被释放,那么你得到的s1的地址自然就没有意义了,即使你得到的字符串是对的,因为可能foo释放之后的空间尚未被新的函数申请。所以,这个是未定义的。
你的解答比较清晰,我翻了翻网上文章,确实是这样的。 所以,正确的做法是: a、在函数内分配堆空间,数据复制到对空间,由指针返回使用后释放吗? 还是, b、在调用前分配好空间,函数内通过指针把数据写入到先前分配好的空间? a的问题在于分配空间和释放空间不在一处,难免犯差错 b的问题在于不知道函数内的数据有多大、需要分配多少空间承载数据 这方面的“最佳实践”,有示范的代码展示一下吗?[/quote] 为了能在main函数里得到字符串的地址,方法就是加static const char *s1[];原因是s1是局部变量,生命周期是函数内,函数调用结束,s1的生命就随着结束,加了static,那么它的生命周期就是整个程序,程序结束s1也随着释放。 当然也有其他方法,就是在堆栈上申请s1(2个指针大小空间),因为堆上的空间不会随着函数释放而释放,它可以在main函数里释放,前提是main函数尤其申请的堆上空间的首地址。[/quote] 静态方法不是很好的选择,说到底我要给这个C++接口做一个C的封装: ============================================= int segmentor_segment(void *segmentor, const std::string &line, std::vector<std::string> &words) 功能:调用分词接口。 参数: 参数名 参数描述 void * segmentor 分词器的指针 const std::string & line 待分词句子 std::vector<std::string> & words 结果分词序列 返回值: 返回结果中词的个数。 ============================================= 要把words的数据返回给C使用。words有可能很大,每个token空间都是static的话应该是不可行的。 [/quote] 你这个可以搞个局部变量的数组,传给函数作为函数的返回值额。比如 words[MAX_WORD_COUNT][MAX_WORD_LENGTH],一个句子的词的个数,和每个词的长度肯定是有上界的吧,就用这种数组传参进入接收函数的输出。
赵4老师 2017-05-22
  • 打赏
  • 举报
回复
引用 12 楼 zhao4zhong1 的回复:
这个世界上根本就没有那么多“不确定”。 在现实世界中,除时间和空间可能是无限的以外,其它任何事物都是有限的。
参考下面:
//使用动态分配
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int i,L;
char *p;
void main() {
    for (i=0;i<20000;i++) {
        L=rand();
        p=malloc(L);
        if (NULL==p) {
            printf("malloc error!\n");
            continue;
        }
        memset(p,0,L);
        free(p);
    }
}
//不使用动态分配
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#define MAXLEN 30000
int i,L;
char buf[MAXLEN];
char *p;
void main() {
    p=&buf[0];
    for (i=0;i<20000;i++) {
        L=rand();
        if (L>MAXLEN) {
            printf("L>MAXLEN==%d, ignore spilth.\n",MAXLEN);
            L=MAXLEN;
        }
        memset(p,0,L);
    }
}
个人倾向不使用动态分配。
自信男孩 2017-05-22
  • 打赏
  • 举报
回复
引用 8 楼 seai 的回复:
[quote=引用 7 楼 cfjtaishan 的回复:] [quote=引用 6 楼 seai 的回复:] [quote=引用 5 楼 cfjtaishan 的回复:] foo的操作获得的s1的地址是没有意义的,得到的结果也是未定义的。因为s1是在foo函数栈上申请的局部变量指针数组,foo调用结束后,s1指针数组就会被释放,那么你得到的s1的地址自然就没有意义了,即使你得到的字符串是对的,因为可能foo释放之后的空间尚未被新的函数申请。所以,这个是未定义的。
你的解答比较清晰,我翻了翻网上文章,确实是这样的。 所以,正确的做法是: a、在函数内分配堆空间,数据复制到对空间,由指针返回使用后释放吗? 还是, b、在调用前分配好空间,函数内通过指针把数据写入到先前分配好的空间? a的问题在于分配空间和释放空间不在一处,难免犯差错 b的问题在于不知道函数内的数据有多大、需要分配多少空间承载数据 这方面的“最佳实践”,有示范的代码展示一下吗?[/quote] 为了能在main函数里得到字符串的地址,方法就是加static const char *s1[];原因是s1是局部变量,生命周期是函数内,函数调用结束,s1的生命就随着结束,加了static,那么它的生命周期就是整个程序,程序结束s1也随着释放。 当然也有其他方法,就是在堆栈上申请s1(2个指针大小空间),因为堆上的空间不会随着函数释放而释放,它可以在main函数里释放,前提是main函数尤其申请的堆上空间的首地址。[/quote] 静态方法不是很好的选择,说到底我要给这个C++接口做一个C的封装: ============================================= int segmentor_segment(void *segmentor, const std::string &line, std::vector<std::string> &words) 功能:调用分词接口。 参数: 参数名 参数描述 void * segmentor 分词器的指针 const std::string & line 待分词句子 std::vector<std::string> & words 结果分词序列 返回值: 返回结果中词的个数。 ============================================= 要把words的数据返回给C使用。words有可能很大,每个token空间都是static的话应该是不可行的。 [/quote] 不要把静态方法和静态变量混淆了,静态方法是static限制函数的,静态变量是在静态数据段上存放,对数组s1加static是改变s1的生命周期,这样可以在main函数里,在foo调用结束后s1没有被释放。
赵4老师 2017-05-22
  • 打赏
  • 举报
回复
这个世界上根本就没有那么多“不确定”。 在现实世界中,除时间和空间可能是无限的以外,其它任何事物都是有限的。
seai 2017-05-22
  • 打赏
  • 举报
回复
引用 10 楼 zhao4zhong1 的回复:
在整个程序初始化时分配该程序将要用到的所有空间; 在整个程序退出前释放该程序在初始化时分配的所有空间。
但这样一来,接口会返回多少字符串是不确定的,所需的空间也就不确定的,和开始就分配好空间呢?
赵4老师 2017-05-22
  • 打赏
  • 举报
回复
在整个程序初始化时分配该程序将要用到的所有空间; 在整个程序退出前释放该程序在初始化时分配的所有空间。
yangyunzhao 2017-05-22
  • 打赏
  • 举报
回复
引用 6 楼 seai 的回复:
[quote=引用 5 楼 cfjtaishan 的回复:] foo的操作获得的s1的地址是没有意义的,得到的结果也是未定义的。因为s1是在foo函数栈上申请的局部变量指针数组,foo调用结束后,s1指针数组就会被释放,那么你得到的s1的地址自然就没有意义了,即使你得到的字符串是对的,因为可能foo释放之后的空间尚未被新的函数申请。所以,这个是未定义的。
你的解答比较清晰,我翻了翻网上文章,确实是这样的。 所以,正确的做法是: a、在函数内分配堆空间,数据复制到对空间,由指针返回使用后释放吗? 还是, b、在调用前分配好空间,函数内通过指针把数据写入到先前分配好的空间? a的问题在于分配空间和释放空间不在一处,难免犯差错 b的问题在于不知道函数内的数据有多大、需要分配多少空间承载数据 这方面的“最佳实践”,有示范的代码展示一下吗?[/quote] 比较保险的做法是

char * getstr()
{
 char * pret = new char[XXX];
......;
return pret;
}
void release(char *& ptr)
{
delete []ptr;
ptr = NULL;
}
这样跨DLL使用也没有问题
seai 2017-05-21
  • 打赏
  • 举报
回复
引用 3 楼 zhao4zhong1 的回复:
业界的通行做法是传指向大量共享数据的指针。
所以,技术上,我上面的做法是没有问题的咯。在函数外部访问的时候,指针所指向的那些数据,我不需要担心被释放?
赵4老师 2017-05-21
  • 打赏
  • 举报
回复
业界的通行做法是传指向大量共享数据的指针。
seai 2017-05-21
  • 打赏
  • 举报
回复
引用 1 楼 zhao4zhong1 的回复:
请那些喜欢将数组作为函数参数传来传去或作为函数返回值的码农思考一下为什么不把整个互联网内容当作函数参数传来传去或作为函数返回值呢?
亲,谢谢你的回复。 首先,我最关注的还是我的上面的担心是否正确,还是说,可以技术上是可行可靠的。 此次,我demo的这段代码,之所以这样做,其实恰是因为,我调用的一个c++动态库接口,它的返回值表示成功与否,实际的数据是用参数传出的。我需要给它做一个C的封装,然后就有了上面的路子。如果有更好的方法,请明示。本人C/C++只能算业余的。 多谢了。
赵4老师 2017-05-21
  • 打赏
  • 举报
回复
请那些喜欢将数组作为函数参数传来传去或作为函数返回值的码农思考一下为什么不把整个互联网内容当作函数参数传来传去或作为函数返回值呢?
paschen 2017-05-21
  • 打赏
  • 举报
回复
s1数组本身是局部变量,函数结束后就销毁了,结果正确只能说明运气好 字符串"abc"是在常量区,可以从函数返回指向常量字符串的指针 要限制函数外修改,可以限定指针类型,让指针指向的字符串为常量
seai 2017-05-21
  • 打赏
  • 举报
回复
引用 7 楼 cfjtaishan 的回复:
[quote=引用 6 楼 seai 的回复:] [quote=引用 5 楼 cfjtaishan 的回复:] foo的操作获得的s1的地址是没有意义的,得到的结果也是未定义的。因为s1是在foo函数栈上申请的局部变量指针数组,foo调用结束后,s1指针数组就会被释放,那么你得到的s1的地址自然就没有意义了,即使你得到的字符串是对的,因为可能foo释放之后的空间尚未被新的函数申请。所以,这个是未定义的。
你的解答比较清晰,我翻了翻网上文章,确实是这样的。 所以,正确的做法是: a、在函数内分配堆空间,数据复制到对空间,由指针返回使用后释放吗? 还是, b、在调用前分配好空间,函数内通过指针把数据写入到先前分配好的空间? a的问题在于分配空间和释放空间不在一处,难免犯差错 b的问题在于不知道函数内的数据有多大、需要分配多少空间承载数据 这方面的“最佳实践”,有示范的代码展示一下吗?[/quote] 为了能在main函数里得到字符串的地址,方法就是加static const char *s1[];原因是s1是局部变量,生命周期是函数内,函数调用结束,s1的生命就随着结束,加了static,那么它的生命周期就是整个程序,程序结束s1也随着释放。 当然也有其他方法,就是在堆栈上申请s1(2个指针大小空间),因为堆上的空间不会随着函数释放而释放,它可以在main函数里释放,前提是main函数尤其申请的堆上空间的首地址。[/quote] 静态方法不是很好的选择,说到底我要给这个C++接口做一个C的封装: ============================================= int segmentor_segment(void *segmentor, const std::string &line, std::vector<std::string> &words) 功能:调用分词接口。 参数: 参数名 参数描述 void * segmentor 分词器的指针 const std::string & line 待分词句子 std::vector<std::string> & words 结果分词序列 返回值: 返回结果中词的个数。 ============================================= 要把words的数据返回给C使用。words有可能很大,每个token空间都是static的话应该是不可行的。
自信男孩 2017-05-21
  • 打赏
  • 举报
回复
引用 6 楼 seai 的回复:
[quote=引用 5 楼 cfjtaishan 的回复:] foo的操作获得的s1的地址是没有意义的,得到的结果也是未定义的。因为s1是在foo函数栈上申请的局部变量指针数组,foo调用结束后,s1指针数组就会被释放,那么你得到的s1的地址自然就没有意义了,即使你得到的字符串是对的,因为可能foo释放之后的空间尚未被新的函数申请。所以,这个是未定义的。
你的解答比较清晰,我翻了翻网上文章,确实是这样的。 所以,正确的做法是: a、在函数内分配堆空间,数据复制到对空间,由指针返回使用后释放吗? 还是, b、在调用前分配好空间,函数内通过指针把数据写入到先前分配好的空间? a的问题在于分配空间和释放空间不在一处,难免犯差错 b的问题在于不知道函数内的数据有多大、需要分配多少空间承载数据 这方面的“最佳实践”,有示范的代码展示一下吗?[/quote] 为了能在main函数里得到字符串的地址,方法就是加static const char *s1[];原因是s1是局部变量,生命周期是函数内,函数调用结束,s1的生命就随着结束,加了static,那么它的生命周期就是整个程序,程序结束s1也随着释放。 当然也有其他方法,就是在堆栈上申请s1(2个指针大小空间),因为堆上的空间不会随着函数释放而释放,它可以在main函数里释放,前提是main函数尤其申请的堆上空间的首地址。
seai 2017-05-21
  • 打赏
  • 举报
回复
引用 5 楼 cfjtaishan 的回复:
foo的操作获得的s1的地址是没有意义的,得到的结果也是未定义的。因为s1是在foo函数栈上申请的局部变量指针数组,foo调用结束后,s1指针数组就会被释放,那么你得到的s1的地址自然就没有意义了,即使你得到的字符串是对的,因为可能foo释放之后的空间尚未被新的函数申请。所以,这个是未定义的。
你的解答比较清晰,我翻了翻网上文章,确实是这样的。 所以,正确的做法是: a、在函数内分配堆空间,数据复制到对空间,由指针返回使用后释放吗? 还是, b、在调用前分配好空间,函数内通过指针把数据写入到先前分配好的空间? a的问题在于分配空间和释放空间不在一处,难免犯差错 b的问题在于不知道函数内的数据有多大、需要分配多少空间承载数据 这方面的“最佳实践”,有示范的代码展示一下吗?
自信男孩 2017-05-21
  • 打赏
  • 举报
回复
foo的操作获得的s1的地址是没有意义的,得到的结果也是未定义的。因为s1是在foo函数栈上申请的局部变量指针数组,foo调用结束后,s1指针数组就会被释放,那么你得到的s1的地址自然就没有意义了,即使你得到的字符串是对的,因为可能foo释放之后的空间尚未被新的函数申请。所以,这个是未定义的。

70,035

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧