C#指针与C++指针的区别

4

我一直在学习编程,选择C++和C#作为我的第一门语言。更具体地说,我借用了一本旧的C语言书籍来学习C#。我使用Visual Studio Express并编写C++和C#代码。一个吸引我的领域是能够进行直接内存管理。我试图学习使用它来优化我的代码。然而,我在正确使用它并实际看到任何真正的性能提升方面遇到了困难。例如,以下是C#中的代码:

unsafe static void Main(string[] args)
{
    int size = 300000;
    char[] numbers = new char[size];

    for (int i = 0; i < size; i++)
    {
        numbers[i] = '7';
    }

    DateTime start = DateTime.Now;

    fixed (char* c = &numbers[0])
    {
        for (int i = 0; i < 10000000; i++)
        {
            int number = myFunction(c, 100000);
        }
    }

    /*char[] c = numbers;  // commented out C# non-pointer version same 
          speed as C# pointer version
    {
        for (int i = 0; i < 10000000; i++)
        {
            int number = myFunction(c, 100000);
        }
    }*/

    TimeSpan timeSpan = DateTime.Now - start;
    Console.WriteLine(timeSpan.TotalMilliseconds.ToString());
    Console.ReadLine();
}

static int myFunction(ref char[] numbers, int size)
{
    return size * 100;
}

static int myFunction(char[] numbers, int size)
{
    return size * 100;
}

unsafe static int myFunction(char* numbers, int size)
{
    return size * 100;
}

无论我调用哪种方法,执行速度都是相同的。我还在努力理解使用ref和使用指针之间的区别,但这可能需要时间和实践。然而,我不明白的是,我能够在C++中产生非常显著的性能差异。当我尝试在C++中近似相同的代码时,我得到了以下结果:
/*int myFunction(std::string* numbers, int size)  // C++ pointer version commented 
     out is much faster than C++ non-pointer version
{
    return size * 100;
}*/

int myFunction(std::string numbers, int size) // value version
{
    return size * 100;
}

int _tmain(int argc, _TCHAR* argv[])
{
int size = 100000;
std::string numbers = "";
for (int i = 0; i < size; i++)
{
    numbers += "777";
}

clock_t start = clock();

for (int i = 0; i < 10000; i++)
{
    int number = myFunction(numbers, 100000);
}

clock_t timeSpan = clock() - start;

std::cout << timeSpan;
char c;
std::cin >> c;

return 0;
}

有谁能告诉我为什么我的C#代码没有从我的引用或指针使用中受益呢?我一直在阅读网上的东西,但我卡住了。


1
只是一个小建议;使用/**/来注释它们之间的所有内容,而不是在每一行上使用// - Prime
4
在现实世界中使用C#,除了某些平台交互和可能的图像处理之外,您很可能永远不需要使用不安全代码。C#不是用于直接管理内存的,因此提供了垃圾回收等功能。因此,在大多数情况下,它不会从直接内存操作中获得性能上的好处。这是因为它不会编译为机器代码,仍然在CLR上执行。另一方面,C++直接编译为机器代码并几乎完全按原样执行您的代码,因此内存优化有效。 - Ruslan
2
C# 对象默认情况下是按引用传递的,因此即使使用昂贵的大型复制看起来像按值传递,实际上仍然是引用,只有在必要时(修改)才会复制对象。 - Luka Rahne
2
我认为在一个非常紧密的循环中做一百万次简单乘法运算甚至不足以在执行时间方面产生任何影响。而且你甚至没有对数组/指针做任何操作,那你到底在计时什么? - Jeff Mercado
2
不要用C语言的书来学习C# - 那只会让你更加困惑。这两种语言的语法有些相似,但是很多东西都是不同的。 - Jean Hominal
显示剩余7条评论
2个回答

7
C#可以在不显式声明的情况下生成指针。每个引用类型引用(如您的numbers变量),实际上在运行时都是一个指针。每个使用refout关键字传递的参数,在运行时实际上都是指针。您的数组参数的精确C等效项为char**,在C++中为char*&。在C#中没有区别。
因此,您不会看到任何速度差异,因为实际执行的代码是相同的。
它并不仅仅是这样,您实际上从未对数组进行任何操作。您调用的方法在运行时消失,就像在C或C++编译器中一样,它将被优化器内联inline。由于您没有使用数组参数,因此也不会得到与之相关的任何代码。
当您使用指针实际上寻址内存时,指针变得有用起来,以加快程序速度。您可以对数组进行索引,并确信您永远不会为数组边界检查付出代价。在许多情况下,即使在正常使用时,您也不会为其付费。Jitter优化器对于如果知道索引始终安全,则删除检查相当聪明。这是指针的unsafe使用,您可以轻松地将其涂写到不属于数组的内存部分中,并以此破坏GC堆。用于对象引用或ref参数的指针从未不安全。
唯一真正看到这一切的方法是查看生成的机器代码。调试+窗口+反汇编窗口。即使在您调试它时,仍然允许对代码进行优化,否则您将无法看到优化结果。确保运行发布版本,使用工具+选项,调试,常规,取消选中"Suppress JIT optimization on module load"选项。需要对机器代码有一定的了解才能理解所看到的内容。

不完全相同。ref char[] 是指向指针的指针,而另外两个是单个指针。 - Ben Voigt
值得一提的是,在C++示例中,一个std::string被传递到函数中时是按值传递的。这与传递指向数据的指针或引用不同,而是传递整个数据内容。 - Jeff Mercado
我需要一些时间来思考你提出的观点。但是,有一个问题我无法理解,就是当你说“你调用的方法在运行时消失了,就像在C或C++编译器中一样”时。如果方法在运行时消失了,为什么我会看到在C++程序执行速度方面通过指针传递和通过值传递之间存在差异,如果方法在运行时消失了?只是为了确保我没有误传我的问题,我不是将C#执行速度与C++执行速度进行比较。我正在比较C#值与指针之间的差异与C++中的差异。 - ostrichchasedwormaroundfield
在C++中按值传递对象是昂贵的,因为它必须复制对象。在C++中,所有非指针类型都是值类型。在C#中不可能这样做,像数组或字符串这样的引用类型总是按引用传递。 - Hans Passant

2
问题在于你测量的不是你认为的东西。我可以阅读你的代码并立即看出为什么会得到这个结果或那个结果,这不仅仅因为指针或非指针。还有许多其他因素在起作用,或者可能会起作用。各种评论反映了这一点。
值得注意的是,一个C++调用比另一个调用慢得多的主要原因是慢的一个会复制std::string,而快的则不会。C#示例之间没有类似那种差异。
我的建议是,作为一个聪明但处于早期阶段的程序员,你首先应该专注于成为一个更好的程序员。在你知道自己想要实现什么之前,不要担心“优化”。
当你准备真正理解这个问题时,你将不得不研究生成的代码。对于C#来说,那就是MSIL,再加上它在特定平台上JIT的内容。对于C++来说,那就是针对特定处理器的Intel操作码。在你知道MSIL、JIT和操作码是什么之前,确切地理解你得到的结果的原因将很难解释。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接