为什么异常处理不好?[已结束]

Why is exception handling bad? [closed]

提问人:joemoe 提问时间:11/15/2009 最后编辑:Trottjoemoe 更新时间:8/23/2018 访问量:44739

问:


想改进这个问题吗?更新问题,以便可以通过编辑这篇文章来用事实和引文来回答。

2年前关闭。

Google 的 Go 语言作为设计选择没有例外,Linux 的 Linus 称例外是废话。为什么?

异常 错误处理 错误报告

评论

6赞 serbaut 10/21/2012
ZeroMQ 的创建者写道,他认为用 C++ 编写它是一个错误(主要是因为错误处理)250bpm.com/blog:4
0赞 Kerrek SB 3/19/2017
Go 可能没有例外,但它有“恐慌”,你可以从中“恢复”(当延迟语句仍在执行时),并提供非本地控制流......
1赞 masterxilo 10/17/2017
这是一篇不错的文章 lighterra.com/papers/exceptionsharmful(异常处理被认为是有害的)
0赞 Shelby Moore III 1/23/2018
Afaics,异常可以在 Go 中使用重要的样板来模拟,尽管这一点对于从语法糖进行转译可能比手动编写样板更有意义。

答:

31赞 Robert Harvey 11/15/2009 #1

异常本身并不坏,但如果你知道它们会经常发生,它们在性能方面可能会很昂贵。

经验法则是,异常应标记异常情况,并且不应使用它们来控制程序流。

评论

9赞 o.k.w 11/15/2009
@Robert:“你不应该用它们来控制程序流”,我没有这样想过,对我来说:P+1的新视角
3赞 Charles Salvia 11/15/2009
这也取决于语言。例如,如果您使用 Java 编程,则很难避免异常。
2赞 Artelius 11/15/2009
@Charles:我认为关键是,在表明错误、系统配置错误或输入不合理的情况下,例外是合适的。大多数 Java 库异常都可以在“正常工作流”代码中避免。
6赞 asveikau 11/15/2009
他们不必花太多钱。例如,您可以实现一个“try”,该“try”的执行时间为零,并根据它在堆栈上看到的调用方地址在表中“抛出”查找异常处理程序。我想说的是,不使用例外的最大原因与性能完全无关。
1赞 martinkunev 5/18/2021
请注意,Python 完全违背了这一经验法则(这是对 Python 的批评)。
9赞 Tim Sylvester 11/15/2009 #2

典型的论点是,没有办法判断特定代码段会出现哪些异常(取决于语言),而且它们太像 s,因此很难在脑海中跟踪执行。goto

http://www.joelonsoftware.com/items/2003/10/13.html

在这个问题上绝对没有共识。我想说的是,从像 Linus 这样的核心 C 程序员的角度来看,异常绝对是一个坏主意。然而,一个典型的 Java 程序员所处的情况却截然不同。

评论

2赞 RCIX 11/15/2009
C 代码有例外,只是方式不同。您需要将对非平凡函数的每次调用都包装在 ifs 中,这使得使用该语言令人头疼!
1赞 Tim Sylvester 11/15/2009
还有/的东西,这是非常糟糕的。setjmplongjmp
9赞 TrueWill 11/15/2009
你真的想听一个重视胶带程序员并且不相信单元测试是必要的人的建议吗?joelonsoftware.com/items/2009/09/23.html
6赞 Petr Gladkikh 4/13/2012
这是讨论中的经典错误(或作弊),其中关于该主题的论点被个性参考所取代。这通常是讨论退化的迹象。
1赞 Jim Balter 8/5/2012
@PetrGladkikh 讨论始于 OP 提到 Linus 的意见......这种作弊被称为诉诸权威的谬误。讨论只能从那里开始,通过提及他的个性来回答为什么莱纳斯不喜欢例外的问题并不是“作弊”。
8赞 paul 11/15/2009 #3

例外情况还不错。它们非常适合C++的RAII模型,这是C++最优雅的地方。如果你已经有一堆代码不是特别安全的,那么它们在上下文中是坏的。如果你正在编写非常低级的软件,比如 linux 操作系统,那么它们就很糟糕了。如果你喜欢在代码中乱扔一堆错误返回检查,那么它们就没有帮助了。如果在引发异常时没有资源控制计划(C++ 析构函数提供),那么它们就很糟糕。

评论

7赞 Mark Ransom 11/15/2009
即使没有例外,RAII 也很有用。
6赞 Greg Rogers 11/17/2009
但是,如果没有 RAII(或其他一些自动资源管理),异常就没有用处。
0赞 Pharap 7/19/2014
+1 表示指出例外不合适的情况,并且例外本身并不是坏事。
-1赞 o.k.w 11/15/2009 #4
  • 未处理的异常通常是不好的。
  • 异常处理不当是坏事(当然)。
  • 异常处理的“好/坏”取决于上下文/范围和适当性,而不是为了这样做。
11赞 ddaa 11/15/2009 #5

从 golang 的角度来看,我想没有异常处理可以保持编译过程简单和安全。

从 Linus 的角度来看,我理解内核代码都是关于极端情况的。因此,拒绝例外是有道理的。

异常在代码中是有意义的,因为可以将当前任务放在地板上,并且常见的案例代码比错误处理更重要。但它们需要从编译器生成代码。

例如,它们在大多数面向用户的高级代码(如 Web 和桌面应用程序代码)中都很好。

评论

1赞 Lothar 2/4/2015
但是,对于内核代码来说,对于长时间运行的本机服务器进程也是如此。
3赞 Walt Howard 5/19/2021
但是高级语言的存在是为了让人类的编程更容易,而不是为了取悦计算机或编译器。
2赞 Mike Chaliy 11/15/2009 #6

从理论上讲,它们真的很糟糕。在完美的数学世界中,你不会遇到异常情况。看看函数式语言,它们没有副作用,所以它们几乎没有非特殊情况的来源。

但是,现实是另一回事。我们总是遇到“出乎意料”的情况。这就是为什么我们需要例外。

我认为我们可以将异常视为 ExceptionSituationObserver 的语法糖。您只会收到异常通知。而已。

对于 Go,我认为他们会引入一些可以处理“意外”情况的东西。我可以猜到,他们会试图让它听起来不那么具有破坏性,作为例外,而更多地作为应用程序逻辑。但这只是我的猜测。

评论

2赞 Stephen C 11/15/2009
“看看函数式语言,它们没有副作用,所以它们几乎没有非特殊情况的来源。这完全是夸大其词。
3赞 Robert Fraser 3/27/2010
数学中的 5/0 是多少?反光球星(200)?平方(-1)?数学有很多特殊情况。
3赞 Mike Chaliy 3/28/2010
这不是执行位置......他们只是没有意义......正因为如此,可以作为例外实施......但也可以作为前提条件的vialations来实现。所以它取决于技术实现。
3赞 Stephen C 4/16/2013
@MikeChaliy - 对不起,但这只是诡辩。你可以应用这种推理来说,任何事情都没有例外情况,永远。实际上,没有意义(或没有确定值)的数学表达式是例外。这并不意味着需要通过抛出和捕获异常来处理它们......但是,如果不这样做,则需要特殊值(如 Inf 和 Nan)或返回多个值的操作。简而言之,这些情况需要特殊处理。
3赞 Arunav Sanyal 10/12/2016
计算机是状态机。不是一个完美的数学世界。
98赞 asveikau 11/15/2009 #7

异常使编写代码变得非常容易,其中引发的异常将破坏不变量并使对象处于不一致的状态。它们基本上迫使你记住,你所做的大多数陈述都可能抛出,并正确处理。这样做可能很棘手且违反直觉。

考虑像这样简单的例子:

class Frobber
{
    int m_NumberOfFrobs;
    FrobManager m_FrobManager;

public:
    void Frob()
    {
        m_NumberOfFrobs++;

        m_FrobManager.HandleFrob(new FrobObject());
    }
};

假设 ,这看起来没问题,对吧?或者也许不是......想象一下,如果 OR 抛出异常。在此示例中,不会回滚 的增量。因此,任何使用此实例的人都将有一个可能损坏的对象。FrobManagerdeleteFrobObjectFrobManager::HandleFrob()operator newm_NumberOfFrobsFrobber

这个例子可能看起来很愚蠢(好吧,我不得不稍微伸展一下自己来构造一个:-)),但是,要点是,如果程序员没有不断地考虑异常,并确保每当有抛出时,状态的每个排列都会回滚,那么你就会遇到麻烦。

举个例子,你可以把它想象成互斥锁。在关键部分中,您依靠多个语句来确保数据结构未损坏,并且其他线程无法看到您的中间值。如果这些陈述中的任何一个只是随机不运行,你最终会陷入一个痛苦的世界。现在去掉锁和并发性,像这样考虑每个方法。如果您愿意的话,可以将每个方法视为对象状态上的排列事务。在方法调用开始时,对象应为干净状态,最后也应为干净状态。在两者之间,变量可能与 不一致,但您的代码最终会纠正这一点。例外的意思是,你的任何一个陈述都可能随时打断你。在每个单独的方法中,您都有责任使其正确并在发生时回滚,或者对操作进行排序,以便抛出不会影响对象状态。如果你弄错了(而且很容易犯这种错误),那么调用者最终会看到你的中间值。foobar

像 RAII 这样的方法,C++程序员喜欢提到这个问题的最终解决方案,在防止这种情况方面有很长的路要走。但它们不是灵丹妙药。它将确保你在投掷时释放资源,但不能让你免于考虑对象状态的损坏和调用方看到中间值的问题。所以,对于很多人来说,更容易说,通过编码风格的法币,没有例外。如果限制编写的代码类型,则更难引入这些错误。如果你不这样做,就很容易犯错。

整本书都是关于C++中的异常安全编码的。很多专家都搞错了。如果它真的那么复杂并且有很多细微差别,也许这是一个好兆头,你需要忽略这个功能。:-)

评论

10赞 ddaa 11/16/2009
有趣的答案,但它并没有反映我的编程经验。所以我想它要么是特定于文化的(在 Java 或 C++ 中可能比 Python 更像一个问题),要么是特定于领域的。
51赞 Robert Harvey 11/18/2009
如果编写正确,则使用 try-catch-finally 模式以托管语言编写的异常不应离开无效状态;由于 Final 块可以保证被执行,因此可以在那里释放对象。其余的应该由超出范围的变量和垃圾回收来处理。
9赞 asveikau 11/18/2009
@ddaa 这个问题在 Python 中肯定是可能的。结果通常是难以重现的错误。也许你特别细致,或者很幸运。但是,你是对的,这在C++中更像是一个问题,其中糟糕的EH最常见的错误是内存泄漏。我试图强调泄漏不是最严重的问题。@Robert GC 将减少内存泄漏,但我不确定托管代码是否会让您免于程序员错误。特别是,如果有人不注意异常安全,因为他们认为这在他们的语言中不是问题,这不是一个好兆头。
5赞 abourget 5/20/2013
@lzprgmr肯定有:异常允许您在代码中的差异位置处理不同的错误类型。处理连接错误可能需要重新连接,但不需要在深度嵌套函数的中间。你想把它泡到连接管理器或其他东西。然后,处理返回值会强制您检查每个调用的错误,并手动冒泡(例如,在连接重置错误的情况下)。此外,返回值在嵌套调用中叠加:func3 可以返回 -1,func2 调用 func3,在他的错误中返回 -2,func3 返回 -1,等等。
5赞 Maarten Bodewes 5/6/2014
我投了反对票,但我推翻了它,因为这是例外被看不起的原因。然而,在我看来,几乎任何方法或代码段都可能失败。不能通过引入每个错误条件的返回值来处理每个错误条件。您将丢失有关错误的信息。认为您可以通过检查每个语句并进行清理来保持所有内容的良好同步,这会导致非常复杂的代码 - 捕获多个语句的错误并清理一两个未进行 GC 的资源要干净得多。
12赞 Jeff Bramwell 11/15/2009 #8

异常本身并不“坏”,只是有时处理异常的方式往往很糟糕。在处理异常时,可以应用一些准则来帮助缓解其中一些问题。其中一些包括(但肯定不限于):

  1. 不要使用异常来控制程序流 - 即不要依赖“catch”语句来更改逻辑流。这不仅会隐藏逻辑周围的各种细节,还可能导致性能不佳。
  2. 当返回的“status”更有意义时,不要从函数中抛出异常 - 仅在特殊情况下抛出异常。创建异常是一项成本高昂且性能密集型的操作。例如,如果调用某个方法来打开文件,但该文件不存在,则引发“FileNotFound”异常。如果调用确定客户帐户是否存在的方法,则返回布尔值,不要返回“CustomerNotFound”异常。
  3. 在确定是否处理异常时,请勿使用“try...catch“子句,除非您可以对例外执行一些有用操作。如果您无法处理异常,则应让它冒泡到调用堆栈中。否则,异常可能会被处理程序“吞噬”,并且详细信息将丢失(除非您重新引发异常)。

评论

5赞 TrueWill 11/15/2009
返回状态是一件棘手的事情。我见过太多的代码,其中 GetCustomer 方法在成功时返回 Customer 实体,在失败时返回 null。在许多情况下,呼叫代码从未检查过结果,而是立即访问了客户。这在大多数时候都有效......
3赞 Chris 11/19/2009
但是,如果 GetCustomer 引发异常而不是返回 null,则客户端代码仍需要处理该异常。无论是通过检查 null 还是通过处理异常,责任都在于客户端代码 - 如果它没有正确地执行某些操作,那么无论哪种方式,迟早都会爆炸。
2赞 Maarten Bodewes 5/6/2014
@TrueWill 支持模板/泛型的语言通过返回 而不是 now 来解决这个问题。例如,刚刚在 Java 8 中引入,从 Guava(和其他人)那里得到了提示。Option<T>null
0赞 TrueWill 5/6/2014
@owlstead是的。喜欢也许是单子。如果您的语言支持它并提供模式匹配,这是一个很好的方法。
2赞 Walt Howard 5/19/2021
这就是人们对异常感到困惑的原因。只有当值是函数应该为你获取的值时,才应该从函数返回值,而不是一些表示“错误”的带外值。您应该如何从返回字符串的函数返回错误代码?它是如此简单,以至于对人们来说太简单了;如果您的函数无法完成其工作,请抛出。就是这样。
52赞 Stephen C 11/15/2009 #9

Go 语言设计 FAQ 中解释了 Go 没有异常的原因:

例外情况也类似。一个 例外的外观设计数量有 被提议,但每个都增加了 语言的复杂性很大 和运行时。就其本质而言, 异常跨越函数,也许 甚至 goroutines;他们有 影响范围广泛。有 也担心他们的影响 会在图书馆上。他们是, 顾名思义,特殊但 具有其他语言的经验 支持他们表明他们有深刻的 对库和接口的影响 规范。如果能 找到一个允许他们的设计 真正非凡,不鼓励 常见错误变成特殊错误 需要 程序员来补偿。

与泛型一样,例外仍然是 未解决的问题。

换句话说,他们还没有弄清楚如何以他们认为令人满意的方式支持 Go 中的异常。他们并不是说例外本身是坏的;

更新 - 2012 年 5 月

Go 设计师现在已经从围栏上爬了下来。他们的常见问题解答现在是这样说的:

我们认为,将异常耦合到控制结构中,就像 try-catch-finally 习语一样,会导致代码复杂。它还倾向于鼓励程序员将太多的普通错误(例如无法打开文件)标记为异常错误。

Go 采取了不同的方法。对于简单的错误处理,Go 的多值返回可以轻松报告错误,而不会使返回值过载。规范错误类型,再加上 Go 的其他功能,使错误处理变得愉快,但与其他语言完全不同。

Go 还具有一些内置功能,可以从真正的异常情况中发出信号并从中恢复。恢复机制仅作为函数状态的一部分在发生错误后被拆除,这足以处理灾难,但不需要额外的控制结构,如果使用得当,可以产生干净的错误处理代码。

有关详细信息,请参阅延迟、紧急和恢复一文。

因此,简短的回答是,他们可以使用多值回报以不同的方式做到这一点。(无论如何,它们确实有一种异常处理形式。


...Linux 成名的 Linus 称例外是废话。

如果你想知道为什么莱纳斯认为例外是废话,最好的办法是寻找他关于这个主题的著作。到目前为止,我唯一找到的是这句话,它嵌入在C++上的几封电子邮件中:

“整个C++异常处理事情从根本上被破坏了。对于内核来说,它尤其坏了。

你会注意到,他特别谈论的是C++异常,而不是一般的异常。(C++ 异常显然确实存在一些问题,使它们难以正确使用。

我的结论是,Linus 根本没有将例外(一般来说)称为“废话”!

30赞 TrueWill 11/15/2009 #10

我不同意“只在特殊情况下抛出例外”。虽然通常是正确的,但它具有误导性。例外情况是针对错误情况(执行失败)。

无论您使用哪种语言,都可以获取一份 Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries(第 2 版)。关于异常抛出的章节是没有对等的。第一版(第二版在我的工作中)的一些引述:

  • 不要返回错误代码。
  • 错误代码很容易被忽略,而且经常被忽略。
  • 异常是报告框架中错误的主要方式。
  • 一个好的经验法则是,如果一个方法没有按照其名称所暗示的方式执行,则应将其视为方法级故障,从而导致异常。
  • 如果可能,请勿对正常控制流使用异常。

有几页关于异常好处的注释(API 一致性、错误处理代码位置的选择、改进的健壮性等)。有一个关于性能的部分,包括几种模式(Tester-Doer、Try-Parse)。

异常和异常处理还不错。与任何其他功能一样,它们可能会被滥用。

评论

5赞 10/12/2012
我不得不不同意这一点。我不反对例外,这本书是必须的,但它偏向于 .NET 开发和 C#。
4赞 jarvisteve 7/18/2014
我知道这很古老,只是想评论一下,.NET 类型和 *nix 类型之间似乎存在一般的风格分歧。我作为 Linux 开发人员使用的所有库都使用返回代码,而我读过的 *nix 风格指南(例如我的公司和 Google 的)只是简单地说“我们不做例外”。只是觉得这很有趣。
1赞 0x6C38 5/28/2016
框架应以不同于最终用户应用程序的方式处理异常。除了抛出异常之外,框架没有办法处理错误,而消费者应用程序却有。
2赞 Walt Howard 5/19/2021
宾果游戏。异常不仅仅是错误条件。它们是您的功能无法完成其工作、无法满足期望的任何情况。它遇到了特殊情况。如果文件丢失,openfile() 应该抛出异常吗?这取决于承诺的内容。如果允许 openfile() 创建文件(如果它不存在),则没有例外。
-1赞 mario 11/13/2010 #11

好吧,这里是无聊的答案。我想这真的取决于语言。如果异常可能遗漏分配的资源,则应避免使用异常。在脚本语言中,它们只是抛弃或跳过应用程序流的某些部分。这本身就很不讨人喜欢,但通过例外来逃避近乎致命的错误是一个可以接受的想法。

对于错误信号,我通常更喜欢错误信号。这一切都取决于 API、用例和严重性,或者日志记录是否足够。此外,我正在尝试重新定义行为。这个想法是“异常”通常是死胡同,但“电话簿”包含有关错误恢复或替代执行路由的有用信息。(还没有找到一个好的用例,但请继续尝试。throw Phonebooks()

1赞 supercat 12/11/2012 #12

C++ 的异常处理范式构成了 Java 的部分基础,反过来又是 .net,引入了一些很好的概念,但也有一些严重的局限性。异常处理的一个关键设计意图是允许方法确保它们满足其后置条件或引发异常,并确保在方法退出之前需要进行的任何清理都将发生。不幸的是,C++、Java 和 .net 的异常处理范式都无法提供任何良好的方法来处理意外因素阻止执行预期清理的情况。这反过来意味着,如果发生意外情况(在堆栈展开期间发生处理异常的C++方法),必须冒着让一切戛然而止的风险,接受这样一种可能性,即由于堆栈展开清理期间发生的问题而无法解决的情况将被误认为可以解决的情况(并且可能是, 如果清理成功),或者接受无法解决的问题(其堆栈展开清理会触发通常可以解决的异常)的可能性,可能会被忽视,因为处理后一个问题的代码声明它“已解决”。

即使异常处理通常很好,但将异常处理范式视为不可接受的,因为这种范式无法提供处理在其他问题后清理时出现的问题的良好方法,这也不是没有道理的。这并不是说不能用异常处理范式来设计一个框架,即使在多次失败的情况下也能确保合理的行为,但目前还没有一种顶级语言或框架可以做到这一点。

0赞 iuri 9/13/2013 #13

对我来说,这个问题很简单。许多程序员不恰当地使用异常处理程序。语言资源越多越好。能够处理异常是件好事。错误使用的一个例子是必须是整数的值,不能验证,或者另一个输入可以除法,并且不能检查除法为零......异常处理可能是一种避免更多工作和艰苦思考的简单方法,程序员可能想做一个肮脏的快捷方式并应用异常处理......“专业代码永远不会失败”这句话可能是虚幻的,如果算法处理的某些问题本身是不确定的。也许在未知的情况下,自然是好的,异常处理程序就会发挥作用。良好的编程实践是一个有争议的问题。

评论

0赞 supercat 9/13/2013
问题不在于(或不应该)代码是否会失败——更大的问题是,如果代码确实失败了,人们在多大程度上关心细节。如果一个人试图加载一个文档,而其中一种“读取数据”方法失败了,那么人们通常并不真正关心哪一个,因为无论如何效果都是一样的:文档无法加载。从概念上讲,异常处理应该对此有好处。问题在于,.NET 和 Java 的异常处理范式并没有提供任何很好的方法来区分应该混为一谈的“无聊”异常和不应该混为一谈的异常。
5赞 Dean Hiller 7/4/2014 #14

因此,异常的一个很好的用例是......

假设您正在做一个项目,每个控制器(大约 20 个不同的主要控制器)都使用操作方法扩展单个超类控制器。然后,每个控制器都会做一堆彼此不同的事情,在一种情况下调用对象 B、C、D,在另一种情况下调用对象 F、G、D。在许多情况下,有大量返回代码并且每个控制器的处理方式都不同,因此存在例外情况。我捣毁了所有这些代码,从“D”抛出了适当的异常,在超类控制器操作方法中捕获了它,现在我们所有的控制器都是一致的。以前 D 为多个不同的错误情况返回 null,我们想告诉最终用户但不能,我不想将 StreamResponse 变成一个讨厌的 ErrorOrStreamResponse 对象(在我看来,将数据结构与错误混合在一起是一种难闻的味道,我看到很多代码返回“Stream”或其他类型的实体,其中嵌入了错误信息(它应该真的是函数返回成功结构或错误结构,我可以对异常与返回代码一起执行)....尽管我有时会考虑使用多响应的 C# 方式,但在许多情况下,异常可以跳过很多层(我不需要清理资源的层)。

是的,我们必须担心每个级别和任何资源清理/泄漏,但总的来说,我们的控制器都没有任何资源需要清理。

谢天谢地,我们有例外,否则我会进行巨大的重构,并在本应是一个简单的编程问题上浪费太多时间。

评论

1赞 Pharap 7/19/2014
+1 很容易成为我读过的使用异常的最佳论据之一。本可以使用更详细的例子(即 UML 图、一些伪代码或一些实际代码),但关于使子类始终如一地执行的观点是一个很好的观点。此外,你的证据是轶事的事实表明,例外在实际情况下是有用的,而有用性是任何语言功能的主要目的。
0赞 Dean Hiller 3/17/2015
另外,如果你在 scala 中,你可以改为返回 Try[ResponseType],它表示异常或实际响应。然后,您可以遵循我上面回避的相同模式,除了将它们放入尝试之外,没有其他实际例外。然后就像您拥有的每个方法都返回我认为需要的 1+n 响应类型。然而,我们在 scala 中返回 Future[response],它的工作方式与 Try 非常相似,但可以帮助进行更多的异步编程。
1赞 kingfrito_5005 6/17/2015 #15

我还没有阅读所有其他答案,所以这个马已经提到过了,但一个批评是它们会导致程序在长链中断,使得在调试代码时难以跟踪错误。例如,如果 Foo() 调用 Bar(),而 Bar() 调用 Wah(),后者调用 ToString(),那么不小心将错误的数据推送到 ToString() 中最终会看起来像 Foo() 中的错误,这是一个几乎完全不相关的函数。

评论

1赞 ddekany 1/9/2021
至少在 Java 中,您将为每个异常提供堆栈跟踪,该异常将显示整个调用链,其中包含 ToString()。
0赞 Walt Howard 5/19/2021
您可以向 C++ 添加堆栈跟踪。使用 Google。亲自写一行,比如:string data = Socket(TCP)。连接(“75.32.44.23”)。ReadAll() 如果比 SOCKET 更易读 s = socket(TCP);if (if s == -1) 返回 errono;int rval = s.connect(“75.32.44.23”);if (rval == -1) 返回 errno;字符缓冲区[1024];int rval = s.read(sizeof(buffer), buffer);if (rval == -1) 返回 errno;返回缓冲区;<---错误,无法返回基于堆栈的缓冲区。