【问题标题】:Why global variable behaves differently in different methods?为什么全局变量在不同的方法中表现不同?
【发布时间】:2015-09-15 19:09:21
【问题描述】:

目标:

  1. 鉴于大小只能在函数load() 中计算,请创建字符串(字典)的全局数组。
  2. 使用函数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] = '\0';
            
            //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]);
}

【问题讨论】:

  • 我只知道unpure c的答案。
  • 这应该是格式最简洁的问题之一。
  • @WedaPashi 它是由 OP 形成的,但用户也将它塑造成 @MohitJain
  • 是的,我看到了编辑历史。做得很好@MohitJain
  • 1) 始终检查 (!=NULL) fopen() 的返回值以确保操作成功。 2) 始终检查 (!=-1) fseek() 的返回值以确保操作成功。 3) 始终检查 (!=NULL) malloc() 的返回值以确保操作成功

标签: c arrays string variables segmentation-fault


【解决方案1】:

答案很简单,您将指针分配给load() 的本地变量,并在load() 返回时将其释放,因此它在print() 中无效,从而导致未定义的行为。

你甚至评论了它

// local array <-- this is your comment not mine
char *dict[dict_size];

你有两个选择:

  1. 不要使用全局变量,使用全局变量的模式没有任何好处,反而非常危险。您可以从函数load() 返回动态分配的指针,然后将其传递给print()

  2. 使用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] = '\0';

            //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]);
}

【讨论】:

  • 不同意,你可以用或不用全局变量做同样的事情。其实大多数时候你看到这个问题,都是从返回一个指向局部变量的指针来的。
  • 是的,你可以,但是当我想到编译器警告时,我意识到在这种情况下编译器可能不会发出警告,而当你返回局部变量的地址时它肯定会触发警告,我从来不需要全局变量,所以我认为它们仅在特殊情况下才有用。
  • 第二个问题是char *dict[dict_size]会炸掉本地栈。
  • 好点子,没想到dict_size能这么大。
  • 不,当您使用malloc() 创建一个指针时,它在您调用free() 之前一直有效,因此您将在程序运行的同时在整个程序中获得可用的数据。跨度>
【解决方案2】:

load() 中,您创建并填充一个局部变量char *dict[dict_size];,然后您只需将指针global_dict 分配给该变量。但是一旦load() 返回,您就不能再访问任何局部变量。 那是stealing the hotel keys,因为它在那个答案中得到了很好的解释

【讨论】:

  • 所以,几乎完整的输出只是一个意外......明白了。
  • 代码传递的是dict的内容,而不是dict的地址。所以当 load() 退出时内容不会丢失。不幸的是,只传递了第一个地址,而不是作为 dict 实际内容的整个数组。并且由于 dict 数组没有 malloc'd,所以数组的所有其余部分都丢失了
  • 找到反证:dict 的每个元素都在load() 的末尾附近是malloc,所以所有地址都必须在程序结束之前,如果我开始使用正确的(global_dict = dict),所有其他人也应该工作,他们几乎工作到最后。为什么不直到最后?
  • dict 的每个元素都是malloc'd 是对的,所以理论上它们不会丢失。但是dict 本身是指向元素地址的指针数组丢失了,因此在load() 完成后,您没有定义的方法来访问分配的内存。可能会发生存储它们的堆栈部分尚未被覆盖,因此您的程序似乎可以正常工作,但您不能假设
  • 正确;甚至对于dict[0],离开load()后也不能保证有正确的值
【解决方案3】:

问题解释(受@Ingo Leonhardt 启发)

这里的问题:

char *dict[dict_size];

通过这样的声明,数组的内存是自动分配的(仅在load() 中是不可触及的)并且在load() 调用dict 之后可以访问内存以进行覆盖。似乎覆盖发生在程序末尾的某个地方(偶然)。

解决方案(受@iharob 启发)

char **dict = malloc((1 + dict_size) * sizeof(*dict));

因此我为dict 动态分配内存(直到程序结束或free(global_dict) 都无法触及)

附: 我同意全局变量很危险,但用这种设计解决问题是分配的约束。

【讨论】:

    猜你喜欢
    • 2021-08-03
    • 1970-01-01
    • 2014-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多