【问题标题】:Interview questions about string and char关于字符串和字符的面试题
【发布时间】:2017-08-11 12:49:40
【问题描述】:

我遇到了一家公司提出的两个问题。这两个问题让我很困惑。 谁能帮忙解释一下答案的原因?

  1. 写出结果。

    void Test(void){
      char *str = (char *) malloc(100);
      strcpy(str, “hello”);
      free(str);
      if(str != NULL){
        strcpy(str, “world”);
        printf(str);
      }
    } 
    

    Ans:它会输出“world”

  2. 写出结果。

    char *GetMemory(void){
      char p[] = "hello world";
      return p;
    }
    
    void Test(void){
      char *str = NULL;
      str = GetMemory();
      printf(str);
    }
    

    Ans:输出未知,因为指针无效。

【问题讨论】:

  • 1. UB 2. UB .....
  • 第一个问题给出的答案不正确。当您执行约束违规时,您无法预测任何事情。
  • 我建议你阅读一下undefined behavior
  • 我不得不嘲笑strcpy(str, “hello”); 后面几行是if(str != NULL) ...
  • 很明显,它们是用来判断程序员是否知道自己在做什么或者是否是“编写错误的程序员”的技巧问题。任何开始喃喃自语“它会输出这个,因为......”的人都不应该被雇用。

标签: c string char malloc free


【解决方案1】:

两个示例都包含相同类型的错误,在对象的生命周期结束后使用它。

这样做会导致未定义的行为1。文中使用了“引用”一词,这基本上意味着访问一个对象或使用该对象的标识符。

在这两个示例中实际导致未定义行为的不是上面提到的规则1,而是另一个密切相关的规则。指向生命周期已结束的对象的指针的值是不确定的2。读取此类值会导致未定义的行为。这在两个示例中都会发生。

在第一个示例中,str 的生命周期在调用 free 时结束。然后在 if 语句 if(str != NULL){ 中使用该指针,这会导致未定义的行为。

在第二个例子中,p 的生命周期在函数返回时结束。返回的指针被分配给一个指针 str:str = GetMemory();,这会导致未定义的行为。


1(引自:ISO/IEC 9899:201x 6.2.4 对象的存储持续时间 2)
如果一个对象在其外部被引用 生命周期,行为未定义。

2(引自:ISO/IEC 9899:201x 6.2.4 对象的存储持续时间 2)
当指针的值变得不确定时 它指向(或刚刚过去)的对象达到其生命周期的终点。

【讨论】:

    【解决方案2】:

    这两个例子都是“未定义行为”的经典案例。在第一种情况下,您在释放内存后使用的是 malloc 的内存。在第二种情况下,您将返回一个指向其内存在堆栈上的字符串的指针 - 一旦执行 GetMemory 中的返回,该指针将不再有效。

    然而,“未定义的行为”意味着编译器可以做任何它喜欢的事情,而在这两种情况下,编译器很可能只会让你重用内存。

    在第一种情况下,当您释放内存时,通常会发生的情况是该块只是放在空闲列表中以供以后重用。你会发现你可以strcpy 访问它,但可能会破坏mallocs 的免费列表。但这无关紧要,除非malloc 尝试分配新内存。此外,无论您strcpy 什么,除非malloc 将内存重新分配到其他地方,否则它将保持不变。这就是为什么对于某些实现(可能是大多数实现),第一个示例确实打印了“world”。但是,如果此函数是较大程序的一部分,则以后可能会出现无法解释的崩溃。

    在第二种情况下,字符串中的内存是从堆栈中分配的。在这种情况下,printf 需要堆栈空间,因此它可能会覆盖包含字符串字符的内存。 printf 可能会打印一个垃圾字符串,但是因为您将它作为第一个参数传递,它会尝试替换它在垃圾字符串中找到的任何格式说明符。所以你可能会得到垃圾,或者你可能会崩溃。

    【讨论】:

    • 不,在这两种情况下str 的值都是不确定的;甚至 str == NULL 也会有未定义的行为。
    • @JeremyP 不正确。 C标准说指针在free(ptr)之后变得不确定
    • “未定义的行为并不意味着任何特定实现的完全不可预测的行为。” - 正确的。这意味着 ISO C 标准对此类程序施加了任何特定要求。您的答案的第 2 段和第 3 段完全基于特定实现/平台如何处理这个问题(不告诉哪个实现),并且不能保证所有实现都这样做,甚至相同的实现是必需的这样做。
    • 我的没有,它只是崩溃了。 Ubuntu 上的 GCC 6.3.0。
    • @JeremyP 你是对的,考虑到现代机器的额外假设。但是,该问题没有提供该上下文,您的答案也没有。直言不讳地阅读它似乎是因为您认为未定义的行为在 C 中有些可预测,但绝对不是这种情况。我建议你解释一下在什么情况下是正确的。
    【解决方案3】:

    两个结果都是undefined behaviour

    第一个是因为您在释放指针 (str) 后使用它(free() 在成为块后不会/不能将指针设置为 NULL)。

    第二个是因为你正在使用指向另一个函数的局部变量的指针。

    【讨论】:

    • 您还没有回答问题。提问者想知道为什么在第一种情况下,代码会打印出单词“world”,即使它是 UB。
    • “提问者想知道为什么......即使它是 UB” - 这听起来像是矛盾的说法(或者不理解 UB 是什么)。
    • 发问者得到了代码片段,并被告知它导致“世界”一词被打印出来。我用我的 C 编译器(OS X 上的 clang)进行了验证,它还打印了“世界”这个词(尽管在调用 Test 后我不得不刷新标准输出,因为它最后没有新行)。 “未定义的行为”并不意味着“每次都做一些随机和不同的事情”,它只意味着实现可以做任何它喜欢的事情。
    • @JeremyP 当然,但这并不是说即使是 your 实现也会始终生成将打印world 的代码。这不是实现定义的行为。事实上,指针值变成了indeterminate。它可以被覆盖,寄存器可以用于其他用途,或任何类似的东西。
    • @AnttiHaapala 我说的是实现可以为所欲为。它不必是一致的(尽管我的似乎是在这种特定情况下)。
    猜你喜欢
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    • 2021-10-23
    • 2015-05-10
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    相关资源
    最近更新 更多