Aula 15
Tratamento de Exceções
Eiji Adachi Barbosa
LES / DI / PUC-Rio
Setembro / 2011
Avisos
• Próxima aula ( 3/outubro/2011 ) haverá exercício em sala
de aula valendo ponto
– Apenas o conteúdo da aula de hoje
– Exercício em dupla
– Sem consulta
novembro de 15
Eiji Adachi Barbosa
2
Sumário
• Definições básicas
– O que é função?
– Por que / para que usar funções?
– Como se encerram as funções?
– Problema: e quando funções não se encerram corretamente?
• Tratamento de exceções
– Tipos de tratadores
– Caso de insucesso
– Como tratar exceções
• Em C
• Em linguagens de programação contemporâneas
novembro de 15
Eiji Adachi Barbosa
3
Definições básicas
• O que é uma função?
– Função é uma porção auto-contida de código que:
• possui um nome que pode ser chamado (ativado) de diversos
lugares
• pode retornar zero ou mais valores
• pode depender de e alterar zero ou mais parâmetros
• pode alterar zero ou mais valores do estado do módulo
– variáveis internas
– variáveis globais
• pode criar, ler e destruir arquivos, etc...
TIPO_RETORNO NOME_FUNCAO ( LISTA_PARAMETROS)
{
CORPO_FUNCAO
}
novembro de 15
Eiji Adachi Barbosa
4
Definições básicas
• Por que / para que usar funções?
– Princípio dividir para conquistar
• Dividir sistema em módulos  Dividir algoritmo em funções
• Evitar códigos monolíticos
– Reuso e manutenibilidade
novembro de 15
Eiji Adachi Barbosa
5
Definições básicas
• Encerrando a execução de uma função:
– Chegar ao fim de uma função void
– O comando return <VALUE>
• Encerra a execução de uma função imediatamente
• Se um valor de retorno é informado, a função chamada (callee)
retorna este valor para a função chamadora (caller)
• A transferência de controle é local, i.e., após o return o controle do
fluxo de execução passa da função chamada para a função
chamadora
– O comando exit(int)
• Encerra a execução do programa
1. Executa em ordem reversa todas as funções registradas pela função
int atexit( void (*func)(void) )
2. Todos streams são fechados, todos arquivos temporários são apagados
3. O controle de execução retorna ao ambiente-hospedeiro (host
enviornment) o valor inteiro passado como argumento
novembro de 15
Eiji Adachi Barbosa
6
Definições básicas - Pilha de chamadas
int main() {
firstCall();
return 0;
}
void firstCall(){
printf("First Call\n");
secondCall();
return;
}
int secondCall(){
printf("Second Call\n");
thirdCall();
return 0;
int printf(const char *...)
}
char thirdCall(){
void firstCall()
printf("Third Call\nOK, That’s
enough.\n");
int main()
return '0’;
}
novembro de 15
Eiji Adachi Barbosa
7
Definições básicas - Pilha de chamadas
int main() {
firstCall();
return 0;
}
void firstCall(){
printf("First Call\n");
secondCall();
return;
}
int secondCall(){
printf("Second Call\n");
int printf(const char *...)
thirdCall();
return 0;
int secondCall()
}
char thirdCall(){
void firstCall()
printf("Third Call\nOK, That’s
enough.\n");
int main()
return '0’;
}
novembro de 15
Eiji Adachi Barbosa
8
Definições básicas - Pilha de chamadas
int main() {
firstCall();
return 0;
}
void firstCall(){
printf("First Call\n");
secondCall();
return;
}
int printf(const char *...)
int secondCall(){
printf("Second Call\n");
char thirdCall()
thirdCall();
return 0;
int secondCall()
}
char thirdCall(){
void firstCall()
printf("Third Call\nOK, That’s
enough.\n");
int main()
return '0’;
}
novembro de 15
Eiji Adachi Barbosa
9
Problema
• E o que fazer quando uma condição excepcional (também
chamada de exceção) impede que uma função encerre sua
execução corretamente?
• Exemplos:
– Não há memória disponível quando você tenta alocar memória
dinamicamente
– Um parâmetro de entrada ocasiona uma divisão por zero
– Hardware pára de funcionar corretamente
05/11/2015
Eiji Adachi Barbosa
10
Tratamento de exceções
• Sistemas atuais são cada vez maiores e mais complexos
– Condições excepcionais podem surgir por diversos motivos e
serem até mesmo imprevisíveis
• Sistemas robustos e confiáveis devem prover seus serviços
mesmo sob condições excepcionais
– Sistemas críticos:
• Sistemas bancários
• Controladores de redes elétricas, vôo, usinas nucleares...
• Sistemas embarcados em aviões, submarinos, foguetes...
• Tratamento de exceções é o mecanismo responsável pela
detecção de condições excepcionais e pela associação de
ações corretivas com condições excepcionais
05/11/2015
Eiji Adachi Barbosa
11
Tratamento de exceções
• Por que é importante tratar exceções?
– Você pode irritar o seu usuário
novembro de 15
Eiji Adachi Barbosa
12
Tratamento de exceções
• Por que é importante tratar exceções?
– Você pode irritar o seu usuário
novembro de 15
Eiji Adachi Barbosa
13
Tratamento de exceções
• Por que é importante tratar exceções?
– Os usuários podem perder a confiança no seu produto
– ... e você pode passar uma tremenda vergonha!
novembro de 15
Eiji Adachi Barbosa
14
Tratamento de exceções
• Vídeo do Bill Gates:
– http://www.youtube.com/watch?v=TrAD25V7ll8
novembro de 15
Eiji Adachi Barbosa
15
Tratamento de exceções
• Por que é importante tratar exceções?
– Pode custar milhões de dólares/reais/euros
– Ou até mesmo vidas
– http://www.devtopics.com/20-famous-software-disasters/
novembro de 15
Eiji Adachi Barbosa
16
Tratamento de exceções
• Vídeo do Ariane 5:
– http://www.youtube.com/watch?v=kYUrqdUyEpI
novembro de 15
Eiji Adachi Barbosa
17
Tratamento de exceções - Caso de insucesso
• Em outubro de 1996 o foguete francês Ariane 501 se
autodestruiu 5 minutos após decolar
• Motivo:
– cálculo errado do ângulo de ataque
• Causa:
– O Ariane 5 reutilizou um módulo do Ariane 4 para calcular o
“Alinhamento Interno”, elemento relacionado com a
componente horizontal da velocidade
– O valor gerado pelo módulo do Ariane 4 foi muito maior do que
o esperado pelo módulo do Ariane 5, pois a trajetória do Ariane
5 difere da trajetória do Ariane 4
– O módulo do Ariane 5 tentou converter o valor do “Alinhamento
Interno” de um número de 64 bits em ponto flutuante para um
inteiro de 16 bits com sinal
– Valor em ponto flutuante era maior do que poderia ser
representado por um inteiro
– Operação de conversão não estava protegida e ocasionou
Overflow Exception
novembro de 15
Eiji Adachi Barbosa
18
Tratamento de exceções
• Terminologia básica
– Exceção: Uma exceção (ou condição excepcional) é um evento
que ocorre durante a execução de um programa (ou módulo) e
que impede a sua execução normal
– Tratador: Um tratador é um conjunto de ações que visam lidar
com uma exceção
novembro de 15
Eiji Adachi Barbosa
19
Tratamento de exceções
• Exemplos de tratadores:
– Armazenamento de erro: cria um registro da exceção e de
informações adicionais em arquivo especial (log).
– Notificação ao usuário: notifica o usuário a ocorrência da exceção,
com possibilidade de pedir novo input, caso seja possível.
– Reconfiguração: reconfigura o estado do sistema com outros valores.
– Nova tentativa: a mesma função, ou uma diferente implementação, é
invocada novamente.
– Liberação de recursos: assegura a liberação de recursos alocados,
como memória alocada dinamicamente, arquivos e conexões abertos,
etc.
– Recuperação por retrocesso: desfaz modificações no estado do
sistema a fim de restaurá-lo a um estado válido. Comumente usado em
sistemas de bancos de dados.
– Delegação de controle: delega o controle da execução para outro
trecho do sistema mais apto a lidar com a exceção.
novembro de 15
Eiji Adachi Barbosa
20
Tratamento de exceções
• Exemplos de tratadores:
– Re-sinaliza a exceção: identifica um tipo de exceção e
reporta para a função chamadora outro tipo de exceção.
Geralmente é usada para preservar o acoplamento.
– Ignora a exceção: identifica a ocorrência de uma exceção e
não toma nenhuma ação corretiva. Má prática e deve ser
evitada.
– Preparação para desligar: prepara o sistema para terminar
sem causar efeitos colaterais. Geralmente é usada em
situações extremas. É necessário liberar todos os recursos
alocados e reverter o sistema para um estado em que os dados
estão consistentes.
novembro de 15
Eiji Adachi Barbosa
21
Como tratar exceções?
• A linguagem C não traz suporte específico para tratamento
de exceções
• Fica sob responsabilidade do programador desenvolver uma
forma de identificar e tratar exceções da aplicação
• Existem várias formas de realizar esta tarefa, cada um com
seus prós e contras
• A fim de padronizar a identificação e o tratamento de
exceções em C, usaremos nesta disciplina um idioma de
tratamento de exceções:
– Usando o código de retorno das funções e elementos enum
para indicar sob qual condição a função encerrou sua execução
– Modificando / testando variáveis globais ou parâmetros
passados por referência
novembro de 15
Eiji Adachi Barbosa
22
Usando o código de retorno das funções
• Este é o método mais comum e mais simples de tratamento
de exceções em linguagem C
• Neste método, as funções tem duas responsabilidades:
– A função chamada deve usar o comando return para indicar
sob qual condição (normal | excepcional) sua execução
encerrou
– A função chamadora deve testar o código retornado pela
função chamada a fim de tomar ações corretivas, caso
necessário
• Preferencialmente, as condições de retorno devem ser
declaradas como um elemento enum
novembro de 15
Eiji Adachi Barbosa
23
Usando o código de retorno das funções
typedef enum {
LIS_tpCondRet LIS_InserirElementoAntes
LIS_CondRetOK ,
( LIS_tppLista pLista , void * pValor )
/* Concluiu corretamente */
{
tpElemLista * pElem ;
LIS_CondRetListaVazia ,
/* A lista não contém elementos */
pElem = CriarElemento( pLista , pValor ) ;
if ( pElem == NULL ) {
LIS_CondRetFimLista ,
return LIS_CondRetFaltouMemoria ;
/* Foi atingido o fim de lista */
} /* if */
LIS_CondRetNaoAchou ,
....
/* Não encontrou o valor procurado */
return LIS_CondRetOK ;
LIS_CondRetFaltouMemoria
/* Faltou memória ao tentar criar um
elemento de lista */
} /* Fim função: LIS &Excluir elemento */
} LIS_tpCondRet ;
novembro de 15
Eiji Adachi Barbosa
24
Usando o código de retorno das funções
int main(void){
...
LIS_tpCondRet condRet = InserirElementoAntes( lista, pValor );
switch( condRet ) {
case LIS_CondRetFaltouMemoria:
...
case LIS_CondRetOK:
...
default :
printf(“Condição de retorno inesperada”);
}
}
novembro de 15
Eiji Adachi Barbosa
25
Usando o código de retorno das funções
• Problemas
– Uma função pode usar uma condição de retorno
excepcional cujo valor reside dentro do intervalo de
valores válidos calculados pela função. O problema é que
a função chamadora não sabe se o valor retornado é um
valor válido ou uma condição excepcional
05/11/2015
Eiji Adachi Barbosa
26
Usando o código de retorno das funções
• Problemas:
– Chamadas encadeadas
de funções podem
resultar em uma
estrutura muito
aninhada que é difícil de
compreender, testar e
fazer a manutenção
05/11/2015
ARQ_tpCondRet leArquivo(){
condRet = OK;
abreArquivo();
se( arquivoAberto() ){
determineTamanhoArquivo();
se( determinouTamanho() ){
aloqueMemoria();
se( alocouMemoria() ){
copieDados();
se( ! copiouDados() ){
condRet = ERRO_COPIAR_DADOS;
}
} senão {
condRet = ERRO_ALOCAR_MEM;
}
} senão {
condRet = ERRO_DET_TAM;
}
fecheArquivo();
se( ! fechouArquivo() ){
condRet = ERRO_FECHAR_ARQ;
}
} senão {
condRet = ERRO_ABRIR_ARQ;
}
}
Eiji Adachi Barbosa
27
Usando variáveis globais ou parâmetros
• Este método é complementar ao método de condições de
retorno
– Bastante usado na GLIBC, biblioteca padrão do sistema
GNU/Linux
• Neste método, as funções tem as seguintes
responsabilidades:
– A função chamada deve modificar variáveis globais ou
parâmetros passados por referência para indicar sob qual
condição (normal | excepcional) sua execução encerrou
– A função chamadora deve testar a variável global, ou o
parâmetro passado por referência, a fim de tomar ações
corretivas, caso necessário
novembro de 15
Eiji Adachi Barbosa
28
Usando parâmetro passado por referência
LIS_tpCondRet LIS_InserirElementoAntes
( LIS_tppLista pLista , void * pValor, char ** errorMsg )
{
tpElemLista * pElem ;
pElem = CriarElemento( pLista , pValor ) ;
if ( pElem == NULL ) {
char str[] = “Não foi possível alocar memória para um novo elemento”;
int size = strlen( str ) + 1;
(*errorMsg) = (char*)malloc( sizeof(char) * size );
memcpy( (*errorMsg), str, size );
return LIS_CondRetFaltouMemoria ;
} /* if */
Usar uma variável global,
seria análogo...
....
return LIS_CondRetOK ;
} /* Fim função: LIS &Excluir elemento */
novembro de 15
Eiji Adachi Barbosa
29
Usando parâmetro passado por referência
int main(void){
...
char *errorMsg;
LIS_tpCondRet condRet = InserirElementoAntes( lista, pValor, &errorMsg );
switch( condRet ) {
case LIS_CondRetFaltouMemoria:
printf( “%s”, errorMSG );
case LIS_CondRetOK:
...
default :
printf(“Condição de retorno inesperada”);
}
Usar uma variável global,
seria análogo...
}
novembro de 15
Eiji Adachi Barbosa
30
Limitações de C
• A sinalização de uma exceção não é explícita
– Usa-se o comando return, parâmetros passados por referência
ou variáveis globais
– Não há distinção entre encerramento sob condição normal ou
excepcional
• Nem sempre é possível retornar um elemento enumerado
como condição de retorno
– Ex.: Implemente uma função que receba os três parâmetros
(Nome, Estado Civil, Idade) e retorne por referência a estrutura
preenchida.
• Como prover mais informações a respeito do problema /
exceção?
– Ex.: Qual a severidade? Que condições levaram a esta
ocorrência?
novembro de 15
Eiji Adachi Barbosa
31
Limitações de C
• Há um overhead na criação de tipos enumerados para cada
módulo
– Para cada módulo é definido um tipo enumerado, mesmo que
representem a mesma condição (EX.: Falta de memória)
• A associação entre as exceções descritas nos tipos
enumerados e quais exceções que podem ser levantadas
por uma função depende exclusivamente da especificação
da função
– Difícil entender o acoplamento excepcional entre funções: quais
exceções devem ser tratadas?
• Não há separação textual do código de tratamento de
exceção
– Código torna-se rapidamente extenso, complexo e pouco
compreensível
novembro de 15
Eiji Adachi Barbosa
32
Linguagens contemporâneas
• Linguagens como Java, JavaScript, C++, C#, Python ...
provêem mecanismos de tratamento de exceções
implementados na própria linguagem
• Elementos sintáticos específicos para tratamento de
exceções:
– TRY – define uma região protegida contra a ocorrência de
exceções
– CATCH – define um tratador, i.e., um trecho de código que
implementa um conjunto de ações de recuperação
– FINALLY – define um trecho de código que sempre será
executado, mesmo quando exceções ocorrerem
– THROW – sinaliza a ocorrência de uma exceção
– THROWS – especifica na interface de um módulo / função
quais as possíveis exceções que podem ser ocasionadas
durante a execução daquele módulo / função
novembro de 15
Eiji Adachi Barbosa
33
Acoplamento excepcional explícito
• Cláusula throws indica quais exceções podem ocorrer
durante a execução de uma função
void escreveArquivo(Arquivo) throws
FileNotFoundException,
CharCodingException,
PermissionException;
novembro de 15
Eiji Adachi Barbosa
34
Sinalização explícita de exceções
• Cláusula throw sinaliza a ocorrência de uma exceção
static void escreveArquivo(Arquivo a) throws
FileNotFoundException,
CharCodingException,
PermissionException {
Buffer bf = buscaArquivo( a );
if( bf == null )
throw new FileNotFoundException();
}
novembro de 15
Eiji Adachi Barbosa
35
Melhor separação textual
ARQ_tpCondRet leArquivo(){
condRet = OK;
abreArquivo();
se( arquivoAberto() ){
determineTamanhoArquivo();
se( determinouTamanho() ){
aloqueMemoria();
se( alocouMemoria() ){
copieDados();
se( ! copiouDados() ){
condRet = ERRO_COPIAR_DADOS;
}
} senão {
condRet = ERRO_ALOCAR_MEM;
}
} senão {
condRet = ERRO_DET_TAM;
}
fecheArquivo();
se( ! fechouArquivo() ){
condRet = ERRO_FECHAR_ARQ;
}
} senão {
condRet = ERRO_ABRIR_ARQ;
}
}
novembro de 15
leArquivo(){
try{
abreArquivo();
determineTamanhoArquivo();
aloqueMemoria();
copieDados();
} catch( abrirErro ){...}
catch( determinarTamanhoErro ) {...}
catch( alocarMemoriaErro ) {...}
catch( copiarDadosErro ) {...}
finally {
try{
fecheArquivo();
} catch( fecharArquivoErro ){...}
}
}
Eiji Adachi Barbosa
36
Mecanismo para “código de limpeza”
• O código definido no bloco
finally sempre será
executado, seja após a
terminação normal, ou
após a terminação
excepcional, de um bloco
try
– Usado especialmente para
liberação de recursos,
como memória, arquivos
abertos, conexões abertas,
etc
05/11/2015
leArquivo(){
try{
abreArquivo();
determineTamanhoArquivo();
aloqueMemoria();
copieDados();
} catch( abrirErro ){...}
catch( determinarTamanhoErro ) {...}
catch( alocarMemoriaErro ) {...}
catch( copiarDadosErro ) {...}
finally {
try{
fecheArquivo();
} catch( fecharArquivoErro ){...}
}
}
Eiji Adachi Barbosa
37
Execução e transferência não-local
Lista preencheLista(){
void adiciona(Lista lista, Arquivo arq,
Arquivo arq = null;
int i ) throws RegistroException {
Lista lista = null;
try{
Registro r = leRegistro( arq, i );
arq = abreArquivo( “Dados.txt” );
Elemento e = criaElement( r );
lista = criaLista();
adicionaElemento( lista, e, i );
for( i=0; i<SIZE; i++ ){
}
adiciona( lista, arq, i );
}
//faz mais alguma coisa
} catch( RegistroException ){
print( “Registro lido incorretamente” );
} finally {
if( arq != null){
fechaArquivo( arq );
catch( RegistroException )
}
finally
}
return lista;
novembro de 15
preecheLista()
Eiji Adachi Barbosa
38
Execução e transferência não-local
Lista preencheLista(){
void adiciona(Lista lista, Arquivo arq,
Arquivo arq = null;
int i ) throws RegistroException {
Lista lista = null;
try{
Registro r = leRegistro( arq, i );
arq = abreArquivo( “Dados.txt” );
Elemento e = criaElement( r );
lista = criaLista();
adicionaElemento( lista, e, i );
for( i=0; i<SIZE; i++ ){
}
adiciona( lista, arq, i );
}
//faz mais alguma coisa
} catch( RegistroException ){
print( “Registro lido incorretamente” );
} finally {
if( arq != null){
abreArquivo
fechaArquivo( arq );
catch( RegistroException )
}
finally
}
return lista;
novembro de 15
preecheLista()
Eiji Adachi Barbosa
39
Execução e transferência não-local
Lista preencheLista(){
void adiciona(Lista lista, Arquivo arq,
Arquivo arq = null;
int i ) throws RegistroException {
Lista lista = null;
try{
Registro r = leRegistro( arq, i );
arq = abreArquivo( “Dados.txt” );
Elemento e = criaElement( r );
lista = criaLista();
adicionaElemento( lista, e, i );
for( i=0; i<SIZE; i++ ){
}
adiciona( lista, arq, i );
}
//faz mais alguma coisa
} catch( RegistroException ){
print( “Registro lido incorretamente” );
} finally {
if( arq != null){
criaLista
fechaArquivo( arq );
catch( RegistroException )
}
finally
}
return lista;
novembro de 15
preecheLista()
Eiji Adachi Barbosa
40
Execução e transferência não-local
Lista preencheLista(){
void adiciona(Lista lista, Arquivo arq,
Arquivo arq = null;
int i ) throws RegistroException {
Lista lista = null;
try{
Registro r = leRegistro( arq, i );
arq = abreArquivo( “Dados.txt” );
Elemento e = criaElement( r );
lista = criaLista();
adicionaElemento( lista, e, i );
for( i=0; i<SIZE; i++ ){
}
adiciona( lista, arq, i );
}
//faz mais alguma coisa
} catch( RegistroException ){
print( “Registro lido incorretamente” );
} finally {
leRegistro
if( arq != null){
adiciona
fechaArquivo( arq );
catch( RegistroException )
}
finally
}
return lista;
novembro de 15
preecheLista()
Eiji Adachi Barbosa
41
Execução e transferência não-local
Lista preencheLista(){
void adiciona(Lista lista, Arquivo arq,
Arquivo arq = null;
int i ) throws RegistroException {
Lista lista = null;
try{
Registro r = leRegistro( arq, i );
arq = abreArquivo( “Dados.txt” );
Elemento e = criaElement( r );
lista = criaLista();
adicionaElemento( lista, e, i );
for( i=0; i<SIZE; i++ ){
}
adiciona( lista, arq, i );
Esse trecho de código não é executado!
}
//faz mais alguma coisa
} catch( RegistroException ){
print( “Registro lido incorretamente” );
} finally {
leRegistro
if( arq != null){
adiciona
fechaArquivo( arq );
catch( RegistroException )
}
finally
}
return lista;
novembro de 15
preecheLista()
Eiji Adachi Barbosa
42
Execução e transferência não-local
Lista preencheLista(){
void adiciona(Lista lista, Arquivo arq,
Arquivo arq = null;
int i ) throws RegistroException {
Lista lista = null;
try{
Registro r = leRegistro( arq, i );
arq = abreArquivo( “Dados.txt” );
Elemento e = criaElement( r );
lista = criaLista();
adicionaElemento( lista, e, i );
for( i=0; i<SIZE; i++ ){
}
adiciona( lista, arq, i );
Esse trecho de código não é executado!
}
//faz mais alguma coisa
} catch( RegistroException ){
print( “Registro lido incorretamente” );
} finally {
if( arq != null){
print
fechaArquivo( arq );
catch( RegistroException )
}
finally
}
return lista;
novembro de 15
preecheLista()
Eiji Adachi Barbosa
43
Execução e transferência não-local
Lista preencheLista(){
void adiciona(Lista lista, Arquivo arq,
Arquivo arq = null;
int i ) throws RegistroException {
Lista lista = null;
try{
Registro r = leRegistro( arq, i );
arq = abreArquivo( “Dados.txt” );
Elemento e = criaElement( r );
lista = criaLista();
adicionaElemento( lista, e, i );
for( i=0; i<SIZE; i++ ){
}
adiciona( lista, arq, i );
Esse trecho de código não é executado!
}
//faz mais alguma coisa
} catch( RegistroException ){
print( “Registro lido incorretamente” );
} finally {
if( arq != null){
fechaArquivo( arq );
fechaArquivo
}
finally
}
return lista;
novembro de 15
preecheLista()
Eiji Adachi Barbosa
44
Linguagens contemporâneas
• Principais vantagens em relação a C:
– Redução do aninhamento de estruturas if-then-else
– Melhor separação textual entre o código que implementa a
lógica da aplicação e o código que implementa o tratamento de
exceções
– Também há uma clara distinção entre o encerramento normal
de uma função ( comando return ) e o encerramento
excepcional ( comando throw )
– Tipos de exceção podem ser reutilizadas entre diferentes
módulos
– Mecanismos que garantem a execução de determinados
trechos de código tanto em situações normais, quanto em
situações excepcionais
• Blocos finally
– Liberação de recursos
05/11/2015
Eiji Adachi Barbosa
45
Referência
• Cap. 8 do livro Programação Modular
• http://download.oracle.com/javase/tutorial/essential/exceptions/in
dex.html
novembro de 15
Eiji Adachi Barbosa
46
FIM
novembro de 15
Eiji Adachi Barbosa
47
Download

Tratamento de exceções - PUC-Rio