为什么全局变量在不同的方法中表现不同?
Why global variable behaves differently in different methods?
目标:
- 创建全局字符串数组(字典),因为大小只能在函数中计算
load()
。
- 使用
print()
功能在屏幕上打印词典。
我的做法:
创建指向字符串的全局指针,在 load()
中创建字符串数组并将局部数组分配给全局指针。
问题:
如果我尝试在 load()
内打印全局数组(以及本地数组),一切都很好,但是如果使用 print()
打印,段错误会出现在数组末尾的某处。 GDB 和 valgrind 输出对我来说似乎很神秘。我放弃。怎么了?
来源和词典是here。
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// length of the longest word in dictionary
#define LENGTH 45
// dictionary file
#define DICTIONARY "large"
// prototypes
void load(const char* dictionary);
void print(void);
// global dictionary size
int dict_size = 0;
// global dictionary
char **global_dict;
int main(void)
{
load(DICTIONARY);
print();
return 0;
}
/**
* Loads dictionary into memory.
*/
void load(const char* dictionary)
{
// open dictionary file
FILE *dict_file = fopen(dictionary, "r");
// compute size of dictionary
for (int c = fgetc(dict_file); c != EOF; c = fgetc(dict_file))
{
// look for '\n' (one '\n' means one word)
if (c == '\n')
{
dict_size++;
}
}
// return to beginning of file
fseek(dict_file, 0, SEEK_SET);
// local array
char *dict[dict_size];
// variables for reading
int word_length = 0;
int dict_index = 0;
char word[LENGTH + 1];
// iteration over characters
for (int c = fgetc(dict_file); c != EOF; c = fgetc(dict_file))
{
// allow only letters
if (c != '\n')
{
// append character to word
word[word_length] = c;
word_length++;
}
// if c = \n and some letters're already in the word
else if (word_length > 0)
{
// terminate current word
word[word_length] = '[=10=]';
//write word to local dictionary
dict[dict_index] = malloc(word_length + 1);
strcpy(dict[dict_index], word);
dict_index++;
// prepare for next word
word_length = 0;
}
}
// make local dictioinary global
global_dict = dict;
}
/**
* Prints dictionary.
*/
void print(void)
{
for (int i = 0; i < dict_size; i++)
printf("%s %p\n", global_dict[i], global_dict[i]);
}
答案很简单,您正在将指针分配给 load()
的局部变量,它在 load()
return 时被释放,因此它在 [=15= 中无效],这会导致未定义的行为。
你还评论了它
// local array <-- this is your comment not mine
char *dict[dict_size];
您有两个选择:
不要使用全局变量,你使用全局变量的模式没有任何好处,反而非常危险。您可以 return 从函数 load()
动态分配的指针,然后将其传递给 print()
.
使用malloc()
分配指针数组。
global_dict = malloc(dict_size * sizeof(*global_dict));
为什么我不喜欢全局变量?
- 因为您可以执行您在程序中执行的操作,甚至不会收到编译器的警告。
当然,有了经验就不会做这种事了,所以与其说是全局变量,倒不如说是你的错,但是经常看到还在学习的程序员,用全局变量来解决函数之间共享数据的问题,这就是参数的作用。
所以使用全局变量 + 不知道如何正确处理它们是不好的,而是学习函数参数,你将解决所有需要全局变量通过程序中的不同函数传递数据的问题,而不使用全局变量。
这是你自己的代码,我删除了global_dict
变量,并在load()
中使用了动态内存分配,并且我对malloc()
进行了一些错误检查,你应该改进它如果您希望代码健壮,其余部分不言自明
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// length of the longest word in dictionary
#define LENGTH 45
// dictionary file
#define DICTIONARY "large"
// prototypes
char **load(const char *dictionary);
void print(char **);
int main(void)
{
char **dictionary;
dictionary = load(DICTIONARY);
if (dictionary == NULL)
return -1;
print(dictionary);
/* Don't forget to free resources,
you might need to do it while
the program is still running,
leaked resources might quickly
become a problem.
*/
for (int i = 0 ; dictionary[i] != NULL ; ++i)
free(dictionary[i]);
free(dictionary);
return 0;
}
/**
* Loads dictionary into memory.
*/
char **load(const char *dictionary)
{
// open dictionary file
FILE *dict_file;
size_t dict_size;
char **dict;
char word[LENGTH + 1];
size_t word_length;
size_t dict_index;
dict_file = fopen(dictionary, "r");
if (dict_file == NULL) /* you should be able to notify this */
return NULL; /* failure to open file */
// compute size of dictionary
for (int c = fgetc(dict_file); c != EOF; c = fgetc(dict_file))
{
// look for '\n' (one '\n' means one word)
if (c == '\n')
{
dict_size++;
}
}
// return to beginning of file
fseek(dict_file, 0, SEEK_SET);
// local array
dict = malloc((1 + dict_size) * sizeof(*dict));
/* ^ add a sentinel to avoid storing the number of words */
if (dict == NULL)
return NULL;
// variables for reading
word_length = 0;
dict_index = 0;
// iteration over characters
for (int c = fgetc(dict_file); c != EOF; c = fgetc(dict_file))
{
// allow only letters
if (c != '\n')
{
// append character to word
word[word_length] = c;
word_length++;
}
// if c = \n and some letters're already in the word
else if (word_length > 0)
{
// terminate current word
word[word_length] = '[=12=]';
//write word to local dictionary
dict[dict_index] = malloc(word_length + 1);
if (dict[dict_index] != NULL)
{
strcpy(dict[dict_index], word);
dict_index++;
}
// prepare for next word
word_length = 0;
}
}
dict[dict_index] = NULL;
/* We put a sentinel here so that we can find the last word ... */
return dict;
}
/**
* Prints dictionary.
*/
void print(char **dict)
{
for (int i = 0 ; dict[i] != NULL ; i++)
printf("%s %p\n", dict[i], (void *) dict[i]);
}
在 load()
中,您创建并填充了一个局部变量 char *dict[dict_size];
,然后您只将指针 global_dict
分配给该变量。但是一旦 load()
returns,您就不能再访问任何局部变量。
那是stealing the hotel keys,因为在那个答案
中有很好的解释
问题解释(灵感来自@Ingo Leonhardt)
问题在这里:
char *dict[dict_size];
通过这样的声明,数组的内存被自动分配(它仅在 load()
中不可触及)并且在 load()
调用后 dict
的内存可以访问以进行覆盖。似乎覆盖发生在程序末尾的某个地方(意外)。
解决方案(灵感来自@iharob)
char **dict = malloc((1 + dict_size) * sizeof(*dict));
因此我为 dict
动态分配内存(在程序结束或 free(global_dict)
之前不可触摸)
P.S。
我同意全局变量是危险的,但是用这样的设计解决问题是赋值的约束。
目标:
- 创建全局字符串数组(字典),因为大小只能在函数中计算
load()
。 - 使用
print()
功能在屏幕上打印词典。
我的做法:
创建指向字符串的全局指针,在 load()
中创建字符串数组并将局部数组分配给全局指针。
问题:
如果我尝试在 load()
内打印全局数组(以及本地数组),一切都很好,但是如果使用 print()
打印,段错误会出现在数组末尾的某处。 GDB 和 valgrind 输出对我来说似乎很神秘。我放弃。怎么了?
来源和词典是here。
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// length of the longest word in dictionary
#define LENGTH 45
// dictionary file
#define DICTIONARY "large"
// prototypes
void load(const char* dictionary);
void print(void);
// global dictionary size
int dict_size = 0;
// global dictionary
char **global_dict;
int main(void)
{
load(DICTIONARY);
print();
return 0;
}
/**
* Loads dictionary into memory.
*/
void load(const char* dictionary)
{
// open dictionary file
FILE *dict_file = fopen(dictionary, "r");
// compute size of dictionary
for (int c = fgetc(dict_file); c != EOF; c = fgetc(dict_file))
{
// look for '\n' (one '\n' means one word)
if (c == '\n')
{
dict_size++;
}
}
// return to beginning of file
fseek(dict_file, 0, SEEK_SET);
// local array
char *dict[dict_size];
// variables for reading
int word_length = 0;
int dict_index = 0;
char word[LENGTH + 1];
// iteration over characters
for (int c = fgetc(dict_file); c != EOF; c = fgetc(dict_file))
{
// allow only letters
if (c != '\n')
{
// append character to word
word[word_length] = c;
word_length++;
}
// if c = \n and some letters're already in the word
else if (word_length > 0)
{
// terminate current word
word[word_length] = '[=10=]';
//write word to local dictionary
dict[dict_index] = malloc(word_length + 1);
strcpy(dict[dict_index], word);
dict_index++;
// prepare for next word
word_length = 0;
}
}
// make local dictioinary global
global_dict = dict;
}
/**
* Prints dictionary.
*/
void print(void)
{
for (int i = 0; i < dict_size; i++)
printf("%s %p\n", global_dict[i], global_dict[i]);
}
答案很简单,您正在将指针分配给 load()
的局部变量,它在 load()
return 时被释放,因此它在 [=15= 中无效],这会导致未定义的行为。
你还评论了它
// local array <-- this is your comment not mine
char *dict[dict_size];
您有两个选择:
不要使用全局变量,你使用全局变量的模式没有任何好处,反而非常危险。您可以 return 从函数
load()
动态分配的指针,然后将其传递给print()
.使用
malloc()
分配指针数组。global_dict = malloc(dict_size * sizeof(*global_dict));
为什么我不喜欢全局变量?
- 因为您可以执行您在程序中执行的操作,甚至不会收到编译器的警告。
当然,有了经验就不会做这种事了,所以与其说是全局变量,倒不如说是你的错,但是经常看到还在学习的程序员,用全局变量来解决函数之间共享数据的问题,这就是参数的作用。
所以使用全局变量 + 不知道如何正确处理它们是不好的,而是学习函数参数,你将解决所有需要全局变量通过程序中的不同函数传递数据的问题,而不使用全局变量。
这是你自己的代码,我删除了global_dict
变量,并在load()
中使用了动态内存分配,并且我对malloc()
进行了一些错误检查,你应该改进它如果您希望代码健壮,其余部分不言自明
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// length of the longest word in dictionary
#define LENGTH 45
// dictionary file
#define DICTIONARY "large"
// prototypes
char **load(const char *dictionary);
void print(char **);
int main(void)
{
char **dictionary;
dictionary = load(DICTIONARY);
if (dictionary == NULL)
return -1;
print(dictionary);
/* Don't forget to free resources,
you might need to do it while
the program is still running,
leaked resources might quickly
become a problem.
*/
for (int i = 0 ; dictionary[i] != NULL ; ++i)
free(dictionary[i]);
free(dictionary);
return 0;
}
/**
* Loads dictionary into memory.
*/
char **load(const char *dictionary)
{
// open dictionary file
FILE *dict_file;
size_t dict_size;
char **dict;
char word[LENGTH + 1];
size_t word_length;
size_t dict_index;
dict_file = fopen(dictionary, "r");
if (dict_file == NULL) /* you should be able to notify this */
return NULL; /* failure to open file */
// compute size of dictionary
for (int c = fgetc(dict_file); c != EOF; c = fgetc(dict_file))
{
// look for '\n' (one '\n' means one word)
if (c == '\n')
{
dict_size++;
}
}
// return to beginning of file
fseek(dict_file, 0, SEEK_SET);
// local array
dict = malloc((1 + dict_size) * sizeof(*dict));
/* ^ add a sentinel to avoid storing the number of words */
if (dict == NULL)
return NULL;
// variables for reading
word_length = 0;
dict_index = 0;
// iteration over characters
for (int c = fgetc(dict_file); c != EOF; c = fgetc(dict_file))
{
// allow only letters
if (c != '\n')
{
// append character to word
word[word_length] = c;
word_length++;
}
// if c = \n and some letters're already in the word
else if (word_length > 0)
{
// terminate current word
word[word_length] = '[=12=]';
//write word to local dictionary
dict[dict_index] = malloc(word_length + 1);
if (dict[dict_index] != NULL)
{
strcpy(dict[dict_index], word);
dict_index++;
}
// prepare for next word
word_length = 0;
}
}
dict[dict_index] = NULL;
/* We put a sentinel here so that we can find the last word ... */
return dict;
}
/**
* Prints dictionary.
*/
void print(char **dict)
{
for (int i = 0 ; dict[i] != NULL ; i++)
printf("%s %p\n", dict[i], (void *) dict[i]);
}
在 load()
中,您创建并填充了一个局部变量 char *dict[dict_size];
,然后您只将指针 global_dict
分配给该变量。但是一旦 load()
returns,您就不能再访问任何局部变量。
那是stealing the hotel keys,因为在那个答案
问题解释(灵感来自@Ingo Leonhardt)
问题在这里:
char *dict[dict_size];
通过这样的声明,数组的内存被自动分配(它仅在 load()
中不可触及)并且在 load()
调用后 dict
的内存可以访问以进行覆盖。似乎覆盖发生在程序末尾的某个地方(意外)。
解决方案(灵感来自@iharob)
char **dict = malloc((1 + dict_size) * sizeof(*dict));
因此我为 dict
动态分配内存(在程序结束或 free(global_dict)
之前不可触摸)
P.S。 我同意全局变量是危险的,但是用这样的设计解决问题是赋值的约束。