实现一个新的strcpy函数将重新定义库函数strcpy



据说我们可以写多个声明,但只能写一个定义。现在,如果我用相同的原型实现我自己的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 successfullyn");
    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 successfullyn");
    return 0;
}
char *strcpy(char *destination, const char *source){
    printf("in duplicate function strcpy");
    return "a";
}

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

--以下任何子条款中的每个宏名称(包括将来的库directions)被保留以在包括其任何相关联的报头的情况下按指定使用;除非另有明确说明。

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

--以下任何子条款(包括未来库方向)保留用作宏名称和标识符如果包含任何相关联的标头,则在同一名称空间中的文件作用域。

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

glibc§1.3.3保留名称证明了一个更明确的原因:

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

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

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

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

这几乎可以肯定,因为您传递的目的地是"字符串文字"。

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

由于编译器知道"我可以内联strcpy",所以您的函数永远不会被调用。

您正试图将"b"复制到字符串文字"a"上,但这不起作用。

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

抛开试图修改不可修改内存的问题不谈,请记住,在形式上不允许重新定义标准库函数。

然而,在某些实现中,您可能会注意到,为标准库函数提供另一个定义并不会触发常见的"多重定义"错误。这是因为在这样的实现中,标准库函数被定义为所谓的"弱符号"。例如,GCC标准库就是众所周知的。

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

你可以在这里阅读,例如

如何替换C标准库函数?

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

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

您的问题具有误导性。

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

您只是在尝试写入不可写的内存,即存在字符串文字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标准的无条件保留;你的程序可能不会重新定义这些名称。所有其他库名称如果您的程序明确包含定义或声明它们。出现这种情况有几个原因限制:

[…]

它允许编译器进行任何它喜欢的特殊优化对这些函数的调用,而不可能已由用户重新定义。

您的示例可以这样操作:(使用strdup

char *strcpy(char *destination, const char *source);
int main(){
    char *s = strcpy(strdup("a"), strdup("b"));
    printf("nThe function ran successfullyn");
    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

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

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

我经常使用这个:

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

您也可以通过修改一行来执行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] = '';
}

相关内容

最新更新