据说我们可以写多个声明,但只能写一个定义。现在,如果我用相同的原型实现我自己的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
标志编译代码,并查看编译器为其生成的汇编代码,则不会有call
到strcpy
(因为编译器已经"内联"了该调用,您可以从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
解释此规则的方法是,不能让一个函数的多个定义最终出现在最终链接对象(可执行文件)中。所以,如果链接中包含的所有对象都只有一个函数的定义,那么你就很好了。记住这一点,考虑以下场景。
- 假设您重新定义了某个库中定义的函数somefunction()。你的函数在main.c(main.o)中,在库中,函数在一个名为someobject.o的对象中(在库中)。请记住,在最后一个链接中,链接器只查找库中未解析的符号。因为somefunction()已经从main.o解析,所以链接器甚至不在库中查找它,也不拉入someobject.o。最后的链接只有一个函数定义,一切都很好
- 现在想象一下,在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] = ' ';
}