【问题标题】:Passing a non-empty string to snprintf causes an unrelated char* array to change addresses将非空字符串传递给 snprintf 会导致不相关的 char* 数组更改地址
【发布时间】:2016-06-11 20:44:06
【问题描述】:

我正在做 K&R 书中的练习,在尝试扩展 04-06 以允许具有字符串名称的变量时遇到了一个奇怪的错误。说实话,我实际上已经设法修复了这个错误(很简单——下面解释),但我想知道为什么会首先发生错误。

对于那些不熟悉这个问题的人,你基本上会被要求创建一个命令行计算器(使用波兰符号),它可以存储和调用带有字符名称的变量。

这是出现问题的相关代码:

#define MAXOPLEN 1000

int varCount = 1;
char **keys;
char **values;
// changing the declaration to:
// char strOps[][STROPCOUNT] = { ... };
// fixed the issue
char *strOps[STROPCOUNT] = { "dupe", "swap", "del", "print",
                             "clr", "sin", "cos", "tan",
                             "exp", "pow", "ln", "log",
                             "mem", "re"};

main() {
    keys = malloc(varCount * sizeof(char[MAXOPLEN]));
    keys[0] = "ans";
    values = malloc(varCount * sizeof(char[MAXOPLEN]));
    values[0] = "0.0";

    ... // Other stuff related to the program
}

// flag is unrelated to the problem I'm asking about. It just checks to see
// if the variable name used to store value n is 'ans', which is where
// the last returned value is stored automatically
void memorize(char s[], double n, bool flag) {
    ...  // small conditional block for flag

    for (i = 0; i < varCount; i++) {
        if (equals(keys[i], s)) {
            found = True;
            // Next line is where the program actually breaks
            snprintf(values[i], MAXOPLEN, "%f", n);
            break;
        }
    }

    if (!found) {
        i = varCount;
        varCount++;

        keys = realloc(keys, varCount * sizeof(char*));
        keys[i] = malloc(sizeof(char[MAXOPLEN]));
        keys[i] = s;

        values = realloc(values, varCount * sizeof(char*));
        values[i] = malloc(sizeof(char[MAXOPLEN]));
        snprintf(values[i], MAXOPLEN, "%f", n);
    }
}

编译运行后,第一次输入方程进行计算,一切似乎都运行的很顺利。但是,在调试时,我发现 strOps 中的前三个 char* 奇怪地指向不同的地址。当试图将等式的返回值保存到“ans”时,它进入 memorize() 中的 for 循环,试图查看字符串 s 是否已经被用作键名。它正确地找到keys[0]指向一个匹配s的值(“ans”)的字符串,然后尝试将double n转换为一个字符串并将其保存在values[0]中。

在 snprintf() 函数内部时,strOps 中的前三个 char* 指向 corecrt_stdio_config.h 中此方法内部的其他位置:

_Check_return_ _Ret_notnull_
__declspec(noinline) __inline unsigned __int64* __CRTDECL __local_stdio_printf_options(void)
{
    // Error occurs after this next line:
    static unsigned __int64 _OptionsStorage;
    return &_OptionsStorage;
}

如上面代码中所述,将 strOps 设为 2D 字符数组(而不是 char 指针数组)解决了该问题。这是有道理的,因为字符数组不能改变单个字符的值,但我不明白为什么 corecrt_stdio_config.h 中的那个方法首先改变了这三个指针的值。

谢谢!

【问题讨论】:

  • 开始使用正确的原型函数声明符。 main() 已弃用。并且使用更新的书,K&R 已经过时了。它不教现代C!并查看How to Ask,提供minimal reproducible example。即使您的代码在没有警告的情况下编译,也没有任何意义。
  • 并检查函数的结果是否与进一步执行相关! realloc 可能会失败!

标签: c string stdio


【解决方案1】:

您的初始化不正确并导致更改:

keys[0] = "ans";
values[0] = "0.0";

"ans""0.0"都是字符串字面量,不能用于初始化数组,需要分配后使用strcpy

strcpy (keys, "ans");
strcpy (values, "0.0");

您的另一种选择是一次分配一个字符:

size_t i;
char *p = "ans";
for (i = 0; i < strlen (p); i++)
    keys[i] = p[i];                 /* copy to keys */
p[i] = 0;                           /* nul-terminate */

注意:这些是您的错误示例,您在整个代码中执行相同的操作。

【讨论】:

  • 你能解释一下当你尝试做 keys[0] = "ans"; 时会发生什么吗? ?
  • strOps 是一个指针数组,初始化为字符串文字列表。 keys 是一个 pointer-to-pointer-to char。当您分配keys 时,您会创建一个内存块varCount * sizeof(char[MAXOPLEN])(这是1000 字节,足以容纳~125 uninitialized pointers)。当您分配keys[0] = "ans"; 时,您将"ans"(在只读存储器中)的地址分配给keys 中的第一个指针。然后,当您尝试以 pointer-to-char 的形式访问 keys 时,keys 的地址似乎已更改(因为 数组的地址 是 第一个元素的地址)。
  • 这是有道理的。为什么后来更改 keys[0] 的值会更改 strOps 中的前三个指针?据我所知,它们没有任何关系。
  • 好吧,后来更改keys[0] 并没有改变strOps 中的前三个指针per-se,它改变了strOps 中前三个指针的位置指点。由于"dupe", "swap", ...只读 内存中,它们无法更改,但是您可以更改strOps[0], strOps[1], ... 指向的位置。我需要你的其余代码来指出在哪里,但这大约是 100% 的问题。要将代码块添加到注释中,只需将代码放在backticks 之间(例如`...`
猜你喜欢
  • 2015-09-09
  • 2012-09-12
  • 2023-03-14
  • 2021-10-24
  • 2014-04-30
  • 2015-05-17
  • 2016-02-15
  • 2011-01-21
  • 1970-01-01
相关资源
最近更新 更多