Aggregates are only concerned with DATA MODIFICATION. No two aggregate should be allowed to modify the same data. Since a Value Object is immutable, it prevents this scenario from ever happening. Therefore it is totally fine for two or more aggregates to share the same Value Object as it is a read only data structure and an Aggregate does not care about the read model.
Address a = new Address("1111 ABC Ave.");
person.setAddress(a);
letter.setAddress(a);
person.getAddress().change("2222 XYS Ave.") // THIS IS ILLEGAL SINCE Address is a VO (immutable)
The above will never happen for Address, and so it's not dangerous to share, because nothing you do to the Address of person will ever have an effect on letter, so letter is still protecting it's own invariants.
If Address is made into an entity, then you wouldn't be able to use the same Address in both Entities, since the above code would make letter vulnerable to changes performed on person, and that would break the boundary, and it would prevent letter from being in control of it's invariants.
This is the whole point of Aggregate Roots, it's too model things in a way that limit side effects. If you define very clear modification boundaries, the code will be easier to work with and you'll prevent potential harmful unexpected impact.
I'll add one more thing. As it was mentioned in another answer, you want different Bounded Context to have a different Address type. The reason for that is that the details you need of an Address in one context are not necessarily the same as what you need in another. So by having two Address type, one for each context, you isolate the needs of one from the needs of the other.
Say for shipping you need:
Address
{
Number;
Unit;
Street;
State;
Country;
PostalCode;
}
But for location you need:
Address
{
Number;
Unit;
Latitude;
Longitude;
}
DDD will say, call them both Address, but bound them to different context. So even though in the language they are all talked about as an Address, their specific data and behavior might be different based on the context you are talking about. What you must absolutely not do is create a kind of MonsterAddres that would contain all possible data and behavior of all contexts in your domain and have this be the Address type used in all contexts.
Note that, we are talking about the model in your application, it's ok to store all data of addresses in a Monster Address Table, but when modeling your App, you should separate that into logical bounded context that maps to your domain and the ubiquitous language it employs.