繁体   English   中英

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

[英]Implementing a new strcpy function redefines the library function 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 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";
}

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

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

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

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

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

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

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

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

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

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

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

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

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

您正试图在字符串文字"a"上复制"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 (您的)版本,为什么分段会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标准的所有库类型,宏,变量和函数的名称都是无条件保留的; 您的程序可能不会重新定义这些名称。 如果您的程序明确包含定义或声明它们的头文件,则保留所有其他库名。 这些限制有几个原因:

[...]

它允许编译器在调用这些函数时进行任何特殊的优化,而不会被用户重新定义。

你的例子可以这样运作:( 使用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

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

  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,链接器将抛出错误。

我经常使用这个:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM