【问题标题】:Implementing a new strcpy function redefines the library function strcpy?实现一个新的strcpy函数重新定义了库函数strcpy?
【发布时间】:2013-07-13 15:18:01
【问题描述】:

据说我们可以写多个声明,但只能写一个定义。现在,如果我用相同的原型实现自己的 strcpy 函数:

char * strcpy ( char * destination, const char * source );

那我不是在重新定义现有的库函数吗?这不应该显示错误吗?还是与库函数以目标代码形式提供这一事实有某种关系?

编辑:在我的机器上运行以下代码会显示“分段错误(核心转储)”。我在 linux 上工作并且没有使用任何标志进行编译。

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

char *strcpy(char *destination, const char *source);

int main(){
    char *s = strcpy("a", "b");
    printf("\nThe function ran successfully\n");
    return 0;
}

char *strcpy(char *destination, const char *source){
    printf("in duplicate function strcpy");
    return "a";
}

请注意,我不是在尝试实现该功能。我只是想重新定义一个函数并询问后果。

编辑 2: 应用 Mats 建议的更改后,尽管我仍在重新定义函数,但程序不再给出分段错误。

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

char *strcpy(char *destination, const char *source);

int main(){
    char *s = strcpy("a", "b");
    printf("\nThe function ran successfully\n");
    return 0;
}

char *strcpy(char *destination, const char *source){
    printf("in duplicate function strcpy");
    return "a";
}

【问题讨论】:

  • 你不能那样做,那会崩溃
  • @DGomez 在我的机器上,程序没有崩溃。
  • @SabashanRagavan:首先我没有,然后我做了:同样的结果
  • 正如我在回答中所说,编译器在看到strcpy 时几乎肯定会内联代码 - 这是“你不能用相同的名称编写自己的函数”的动机之一作为库函数”。

标签: c


【解决方案1】:

C11(ISO/IEC 9899:201x) §7.1.3 保留标识符

- 以下任何子条款中的每个宏名称(包括未来库 如果包含任何关联的标头,则保留用于指定用途; 除非另有明确说明。

——在以下任何子条款中具有外部链接的所有标识符(包括 未来的图书馆方向)始终保留用作外部标识符 链接。

— 在以下任何子条款中列出的具有文件范围的每个标识符(包括 未来的库方向)保留用作宏名称和标识符 如果包含任何关联的标头,则文件范围在同一名称空间中。

如果程序在保留标识符的上下文中声明或定义标识符,或将保留标识符定义为宏名称,则行为未定义。请注意,这并不意味着您不能这样做,正如this post 所示,它可以在 gcc 和 glibc 中完成。

glibc §1.3.3 Reserved Names 证明了一个更清楚的理由:

所有来自ISO C标准的库类型、宏、变量和函数的名称都是无条件保留的;您的程序可能不会重新定义这些名称。如果您的程序明确包含定义或声明它们的头文件,则保留所有其他库名称。这些限制有几个原因:

例如,如果您使用名为 exit 的函数执行与标准 exit 函数完全不同的操作,其他阅读您的代码的人可能会感到非常困惑。防止这种情况有助于使您的程序更易于理解,并有助于模块化和可维护性。

它避免了用户意外重新定义由其他库函数调用的库函数的可能性。如果允许重新定义,其他功能将无法正常工作。

它允许编译器在调用这些函数时进行任何它喜欢的特殊优化,而不会被用户重新定义。一些库工具,例如那些用于处理可变参数(参见可变参数函数)和非本地出口(参见非本地出口)的工具,实际上需要 C 编译器方面的大量合作,并且关于实现,编译器可能更容易将它们视为语言的内置部分。

【讨论】:

  • 所以我们在这里处理的是“未定义的行为[u]r”,我猜
  • @WalterTross 据我了解,是的。
  • 你的答案 together 和@MatsPetersson 的答案是正确的答案。如果我是 OP,我不知道该接受哪一个。
  • “未定义的行为”必须是编程术语中最丑陋的短语,就像流行病学家不想考虑“带有空气传播媒介的毒性”这个短语一样......
  • 与这个问题不是特别相关,但有趣的是,即使没有覆盖标准函数名称的问题,all 以“str”开头的标识符都是保留的,所以像strcpy_custom() 这样的东西仍然很糟糕。
【解决方案2】:

这几乎可以肯定是因为您传入的目的地是“字符串文字”。

char *s = strcpy("a", "b");

随着编译器知道“我可以做strcpy inline”,所以你的函数永远不会被调用。

您试图将"b" 复制到字符串文字"a" 上,但这是行不通的。

创建一个char a[2];strcpy(a, "b");,它会运行——它可能不会调用你的strcpy 函数,因为即使你没有可用的优化,编译器也会内联小的strcpy

【讨论】:

  • 我确认纯 gcc 仅在第二个参数是变量时才调用我的 strcpy,如果是字符串文字,不是
【解决方案3】:

将尝试修改不可修改内存的问题放在一边,请记住,正式不允许您重新定义标准库函数。

但是,在某些实现中,您可能会注意到为标准库函数提供另一个定义不会触发通常的“多重定义”错误。发生这种情况是因为在此类实现中标准库函数被定义为所谓的“弱符号”。举个例子,GCC 标准库就是为此而闻名。

这样做的直接后果是,当您使用外部链接定义自己的“版本”标准库函数时,您的定义会覆盖整个程序的“弱”标准定义。您会注意到,不仅您的代码现在调用您的函数版本,而且所有预编译的 [第三方] 库中的所有类也被分派到您的定义中。它旨在作为一项功能,但您必须注意它以避免无意中“使用”此功能。

例如,您可以在此处阅读相关内容

How to replace C standard library function ?

实现的这一特性不违反语言规范,因为它在不受任何标准要求约束的未定义行为的未知区域内运行。

当然,使用某些标准库函数的内部/内联实现的调用不会受到重定义的影响。

【讨论】:

    【解决方案4】:

    您的问题具有误导性。

    您看到的问题与重新实现库函数无关。

    你只是想写不可写的内存,也就是字符串文字a存在的内存。

    简单地说,以下程序在我的机器上给出了分段错误(使用gcc 4.7.3编译,没有标志):

    #include <string.h>
    
    int main(int argc, const char *argv[])
    {
        strcpy("a", "b");
        return 0;
    }
    

    但是,如果您调用不写入不可写内存的strcpy(您的)版本,为什么会出现分段错误?仅仅因为你的函数没有被调用。

    如果您使用-S 标志编译代码并查看编译器为其生成的汇编代码,则不会有callstrcpy(因为编译器已“内联”调用,您可以从 main 中看到的唯一相关调用是对 puts 的调用)。

    .file   "test.c"
        .section    .rodata
    .LC0:
        .string "a"
        .align 8
    .LC1:
        .string "\nThe function ran successfully"
        .text
        .globl  main
        .type   main, @function
    main:
    .LFB2:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movw    $98, .LC0(%rip)
        movq    $.LC0, -8(%rbp)
        movl    $.LC1, %edi
        call    puts
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    .LFE2:
        .size   main, .-main
        .section    .rodata
    .LC2:
        .string "in duplicate function strcpy"
        .text
        .globl  strcpy
        .type   strcpy, @function
    strcpy:
    .LFB3:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movq    %rdi, -8(%rbp)
        movq    %rsi, -16(%rbp)
        movl    $.LC2, %edi
        movl    $0, %eax
        call    printf
        movl    $.LC0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    .LFE3:
        .size   strcpy, .-strcpy
        .ident  "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3"
        .
    

    我认为余浩的回答对此有很好的解释,引用标准:

    所有库类型、宏、变量和函数的名称 来自ISO C标准的无条件保留;您的 程序可能不会重新定义这些名称。所有其他库名称是 如果您的程序明确包含以下头文件,则保留 定义或声明它们。这些有几个原因 限制:

    [...]

    它允许编译器做任何它喜欢的特殊优化 在调用这些函数时,它们不可能 已被用户重新定义。

    【讨论】:

    • @NikunjBanka 我已经用一些汇编代码更新了答案:)
    【解决方案5】:

    你的例子可以这样操作:(with strdup

    char *strcpy(char *destination, const char *source);
    
    int main(){
        char *s = strcpy(strdup("a"), strdup("b"));
        printf("\nThe function ran successfully\n");
        return 0;
    }
    
    char *strcpy(char *destination, const char *source){
        printf("in duplicate function strcpy");
        return strdup("a");
    }
    

    输出:

      in duplicate function strcpy
      The function ran successfully
    

    【讨论】:

      【解决方案6】:

      解释此规则的方法是,您不能让函数的多个定义最终出现在最终链接对象(可执行文件)中。所以,如果链接中包含的所有对象都只有一个函数定义,那么你很好。牢记这一点,请考虑以下场景。

      1. 假设您重新定义了某个库中定义的函数 somefunction()。您的函数在 main.c (main.o) 中,而在库中,该函数位于名为 someobject.o 的对象中(在库中)。请记住,在最终链接中,链接器仅在库中查找 unresolved 符号。因为 somefunction() 已经从 main.o 解析,所以链接器甚至不会在库中查找它,也不会拉入 someobject.o。最后的链接只有一个函数的定义,一切都很好。
      2. 现在假设在 someobject.o 中定义了另一个符号 anotherfunction(),您也碰巧调用了它。链接器将尝试从 someobject.o 中解析 anotherfunction(),并将其从库中拉入,它将成为最终链接的一部分。现在您在最终链接中有两个 somefunction() 定义 - 一个来自 main.o,另一个来自 someobject.o,链接器将抛出错误。

      【讨论】:

        【解决方案7】:

        我经常用这个:

        void my_strcpy(char *dest, char *src)
        {
            int i;
        
            i = 0;
            while (src[i])
            {
                dest[i] = src[i];
                i++;
            }
            dest[i] = '\0';
        }
        

        你也可以通过修改一行来做strncpy

        void my_strncpy(char *dest, char *src, int n)
        {
            int i;
        
            i = 0;
            while (src[i] && i < n)
            {
                dest[i] = src[i];
                i++;
            }
            dest[i] = '\0';
        }
        

        【讨论】:

          猜你喜欢
          • 2013-10-18
          • 2011-09-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-09
          • 1970-01-01
          • 2021-11-11
          • 2020-08-16
          相关资源
          最近更新 更多