这种地址分配和使用方法会调用未定义的行为吗?

标签 c undefined-behavior

这最初发布于 Code Review ,有人建议 Stack Overflow 更适合......

在寻找一种方法来搜索和捕获子字符串时,我编写了以下代码:

char string[] = {"this is the source string to search"};
char substring[] = {"string to"};
char *target;
char buf[80];


int main(void)
{
    target = strstr(string, substring);
    int len = strlen(target);
    strncpy(buf, target, len);
    buf[len]=0;
    return 0;
}

我的问题: 可以按照此处使用的方式使用指针 char *target 可能会调用未定义的行为。如果是,如何?

最佳答案

问题中显示的代码写的是“OK”(略微咬紧牙关),但如果它是一个通用的“从子字符串第一次出现的开始复制到字符串的末尾转换为另一个字符串的函数。

  • 全局变量是不可取的。
  • 缺少单独的功能是不可取的。

更详细:

  • 代码不检查是否在源字符串中找到子字符串;它应该,因为否则代码将取消引用空指针。
  • strncpy()测量子串的长度,假设有一个,然后故意不将空终止符复制到buf中,并故意忽略大小buf,尽管它在 strncpy() 之后明确地以 null 终止复制的数据。使用显示的代码,这无关紧要;字符串足够短并且 buf 足够大(并且 buf 是一个零初始化的全局变量——尽管询问为什么 target 是有效的和 buf 不是局部变量)。
  • 一般来说,这样的编码很草率并且容易出现缓冲区溢出。
  • 您在 CR 上收到的关于使用 memcpy() 的评论是有效的。您也可以使用 len + 1 来复制空字节,从而保存显式赋值,但是您仍然应该限制自己复制的数据不超过 buf 的容量。
  • 您还应该考虑如果字符串太长而无法放入 buf 中是否适合截断,或者您是否真的应该完全拒绝该操作。
  • 尽管在所示代码中没有风险,但我通常建议使用 memmove() 而不是 memcpy(),因为无论源区和目标区重叠。如果您不确定没有重叠,请使用 memmove()

请注意,memmove() 的 C 标准指定(强调):

void *memmove(void *s1, const void *s2, size_t n);

The memmove function copies n characters from the object pointed to by s2 into the object pointed to by s1. Copying takes place as if the n characters from the object pointed to by s2 are first copied into a temporary array of n characters that does not overlap the objects pointed to by s1 and s2, and then the n characters from the temporary array are copied into the object pointed to by s1.

memmove() 的正常实现不会像标准建议的那样将数据复制到中间数组中,除非没有可靠的方法来发现两个数组之间的重叠,这种情况很少见。实现者了解机器的能力,几乎总能避免重复复制。

下面是一个可能的代码实现,如果复制的字符串太大而无法放入目标缓冲区,则会将其截断。可以实现其他行为以满足应用程序设计者的突发奇想。

#include <assert.h>
#include <stdio.h>
#include <string.h>

static void copy_substr(const char *source, const char *substr, char *buffer, size_t buflen)
{
    assert(source != 0 && substr != 0 && substr[0] != '\0' && buffer != 0 && buflen != 0);
    size_t length = 0;
    const char *target = strstr(source, substr);
    if (target != 0)
    {
        length = strlen(target);
        if (length >= buflen)
            length = buflen - 1;    // length = 0 would be an option too
        memmove(buffer, target, length);
    }
    buffer[length] = '\0';  // target might not be null terminated within length
}

int main(void)
{
    char string[] = {"this is the source string to search"};
    char substr[] = {"string to"};
    char buffer[80];
    copy_substr(string, substr, buffer, sizeof(buffer));
    printf("Main string: [%s]\n", string);
    printf("Substring:   [%s]\n", substr);
    printf("Tail string: [%s]\n", buffer);
    return 0;
}

输出:

Main string: [this is the source string to search]
Substring:   [string to]
Tail string: [string to search]

关于这种地址分配和使用方法会调用未定义的行为吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46103705/

相关文章:

c - 在 C 中反转字符串

c - C 中的二叉搜索树实现 - 插入不正确

c - 显示存储在 window dc 中的位图

c - printf中的前自增和后自增

c++ - 此代码如何在没有任何循环语句或 'goto' 或递归的情况下循环?

c++ - c/c++ : i+++++i vs++i + i++ 中的未定义行为

c - 模拟文件更改监视器 (FAM) 事件

我可以在VS2013中正式使用free()吗?

c++ - 我可以获取数组的最后一个元素的地址吗?

C++ 后缀表达式未定义与未指定行为