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

时间:2013-07-13 15:18:01

标签: c

据说我们可以编写多个声明但只能编写一个定义。现在,如果我使用相同的原型实现我自己的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";
}

7 个答案:

答案 0 :(得分:11)

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

  

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

     

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

     

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

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

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

  

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

     

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

     

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

     

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

答案 1 :(得分:7)

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

char * s = strcpy(“a”,“b”);

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

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

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

答案 2 :(得分:4)

除了试图修改不可修改的内存之外,请记住,正式不允许重新定义标准库函数。

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

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

你可以在这里阅读一下这个例子

How to replace C standard library function ?

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

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

答案 3 :(得分:2)

您的问题具有误导性。

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

您只是在尝试编写不可写内存,即存在字符串文字a的内存。

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

#include <string.h>

int main(int argc, const char *argv[])
{
    strcpy("a", "b");
    return 0;
}

但是,如果您调用不写不可写内存的strcpy(您的)版本,为什么分段错误?只是因为你的功能没有被调用。

如果使用-S标志编译代码并查看编译器为其生成的汇编代码,那么call将不会strcpy(因为编译器)已经“内联”了该来电,您可以从主电话中看到的唯一相关电话,是对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标准是无条件保留的;您的   程序可能不会重新定义这些名称。所有其他库名称   如果您的程序明确包含头文件,则保留   定义或声明它们。这有几个原因   限制:

     

[...]

     

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

答案 4 :(得分:1)

您的示例可以通过以下方式运行:( 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

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

我经常使用这个:

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';
}